text: /*
   * oxAuth is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text.
   *
   * Copyright (c) 2014, Gluu
   */
  
  package org.gluu.oxauth.service;
  
  import org.gluu.oxauth.model.config.Conf;
  import org.gluu.oxauth.model.config.ConfigurationFactory;
  import org.gluu.oxauth.model.config.WebKeysConfiguration;
  import org.gluu.oxauth.model.configuration.AppConfiguration;
  import org.gluu.oxauth.model.crypto.AbstractCryptoProvider;
  import org.gluu.oxauth.model.jwk.JSONWebKey;
  import org.gluu.oxauth.service.cdi.event.KeyGenerationEvent;
  import org.gluu.oxauth.util.ServerUtil;
  import org.gluu.persist.PersistenceEntryManager;
  import org.gluu.service.cdi.async.Asynchronous;
  import org.gluu.service.cdi.event.Scheduled;
  import org.gluu.service.timer.event.TimerEvent;
  import org.gluu.service.timer.schedule.TimerSchedule;
  import org.json.JSONArray;
  import org.json.JSONObject;
  import org.slf4j.Logger;
  
  import javax.enterprise.context.ApplicationScoped;
  import javax.enterprise.event.Event;
  import javax.enterprise.event.Observes;
  import javax.inject.Inject;
  import javax.inject.Named;
  import java.util.GregorianCalendar;
  import java.util.TimeZone;
  import java.util.concurrent.atomic.AtomicBoolean;
  import java.util.stream.Collectors;
  
  import static org.gluu.oxauth.model.jwk.JWKParameter.*;
  
  /**
   * @author Javier Rojas Blum
   * @author Yuriy Zabrovarnyy
   */
  @ApplicationScoped
  @Named
  public class KeyGeneratorTimer {
  
  	private static final int DEFAULT_INTERVAL = 60;
  
      @Inject
      private Logger log;
  
      @Inject
      private Event<TimerEvent> timerEvent;
  
      @Inject
      private ConfigurationFactory configurationFactory;
  
      @Inject
      private PersistenceEntryManager ldapEntryManager;
  
      @Inject
      private AppConfiguration appConfiguration;
  
      @Inject
      private AbstractCryptoProvider cryptoProvider;
  
      private AtomicBoolean isActive;
  	private long lastFinishedTime;
  
      public void initTimer() {
          log.debug("Initializing Key Generator Timer");
          this.isActive = new AtomicBoolean(false);
  
  		timerEvent.fire(new TimerEvent(new TimerSchedule(DEFAULT_INTERVAL, DEFAULT_INTERVAL), new KeyGenerationEvent(),
  				Scheduled.Literal.INSTANCE));
  
  		this.lastFinishedTime = System.currentTimeMillis();
      }
  
      @Asynchronous
      public void process(@Observes @Scheduled KeyGenerationEvent keyGenerationEvent) {
          if (!appConfiguration.getKeyRegenerationEnabled()) {
              return;
          }
  
          if (this.isActive.get()) {
              return;
          }
  
          if (!this.isActive.compareAndSet(false, true)) {
              return;
          }
  
          try {
          	updateKeys();
          } catch (Exception ex) {
  			log.error("Exception happened while executing keys update", ex);
          } finally {
              this.isActive.set(false);
          }
      }
  
  	private void updateKeys() throws Exception {
  		if (!isStartUpdateKeys()) {
  			return;
  		}
  
  		updateKeysImpl();
  		this.lastFinishedTime = System.currentTimeMillis();
  	}
  
  	private boolean isStartUpdateKeys() {
  		long poolingInterval = appConfiguration.getKeyRegenerationInterval();
          if (poolingInterval <= 0) {
          	poolingInterval = DEFAULT_INTERVAL;
          }
  
          poolingInterval = poolingInterval * 3600 * 1000L;
  
  		long timeDifference = System.currentTimeMillis() - this.lastFinishedTime;
  
  		return timeDifference >= poolingInterval;
  	}
  
      private void updateKeysImpl() throws Exception {
          log.info("Updating JWKS keys ...");
          String dn = configurationFactory.getBaseConfiguration().getString("oxauth_ConfigurationEntryDN");
          Conf conf = ldapEntryManager.find(Conf.class, dn);
  
          JSONObject jwks = conf.getWebKeys().toJSONObject();
          JSONObject updatedJwks =  updateKeys(jwks);
  
          conf.setWebKeys(ServerUtil.createJsonMapper().readValue(updatedJwks.toString(), WebKeysConfiguration.class));
  
          long nextRevision = conf.getRevision() + 1;
          conf.setRevision(nextRevision);
          ldapEntryManager.merge(conf);
  
          log.info("Updated JWKS successfully");
          log.trace("JWKS keys: " + conf.getWebKeys().getKeys().stream().map(JSONWebKey::getKid).collect(Collectors.toList()));
          log.trace("KeyStore keys: " + cryptoProvider.getKeys());
      }
  
      private JSONObject updateKeys(JSONObject jwks) throws Exception {
          JSONObject jsonObject = AbstractCryptoProvider.generateJwks(cryptoProvider, appConfiguration.getKeyRegenerationInterval(),
                  appConfiguration.getIdTokenLifetime(), appConfiguration);
  
          JSONArray keys = jwks.getJSONArray(JSON_WEB_KEY_SET);
          for (int i = 0; i < keys.length(); i++) {
              JSONObject key = keys.getJSONObject(i);
  
              if (key.has(EXPIRATION_TIME) && !key.isNull(EXPIRATION_TIME)) {
                  GregorianCalendar now = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
                  GregorianCalendar expirationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
                  expirationDate.setTimeInMillis(key.getLong(EXPIRATION_TIME));
  
                  if (expirationDate.before(now)) {
                      // The expired key is not added to the array of keys
                      log.trace("Removing JWK: {}, Expiration date: {}", key.getString(KEY_ID),
                              key.getLong(EXPIRATION_TIME));
                      cryptoProvider.deleteKey(key.getString(KEY_ID));
                  } else if (cryptoProvider.containsKey(key.getString(KEY_ID))) {
                      log.trace("Contains kid: {}", key.getString(KEY_ID));
                      jsonObject.getJSONArray(JSON_WEB_KEY_SET).put(key);
                  }
              } else if (cryptoProvider.containsKey(key.getString(KEY_ID))) {
                  GregorianCalendar expirationTime = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
                  expirationTime.add(GregorianCalendar.HOUR, appConfiguration.getKeyRegenerationInterval());
                  expirationTime.add(GregorianCalendar.SECOND, appConfiguration.getIdTokenLifetime());
                  key.put(EXPIRATION_TIME, expirationTime.getTimeInMillis());
  
                  log.trace("Contains kid {} without exp {}", key.getString(KEY_ID), expirationTime);
  
                  jsonObject.getJSONArray(JSON_WEB_KEY_SET).put(key);
              }
          }
  
          return jsonObject;
      }
  
  }