001package co.codewizards.cloudstore.rest.server.auth; 002 003import static java.util.Objects.*; 004 005import java.util.Arrays; 006import java.util.Comparator; 007import java.util.Date; 008import java.util.HashMap; 009import java.util.Map; 010import java.util.SortedSet; 011import java.util.Timer; 012import java.util.TimerTask; 013import java.util.TreeSet; 014import java.util.UUID; 015 016import co.codewizards.cloudstore.core.auth.AuthToken; 017import co.codewizards.cloudstore.core.config.ConfigImpl; 018import co.codewizards.cloudstore.core.dto.DateTime; 019import co.codewizards.cloudstore.core.util.PasswordUtil; 020 021public class TransientRepoPasswordManager { 022 023 private static final int DEFAULT_VALIDITIY_PERIOD = 60 * 60 * 1000; 024 private static final int DEFAULT_RENEWAL_PERIOD = 30 * 60 * 1000; 025 private static final int DEFAULT_EARLY_RENEWAL_PERIOD = 15 * 60 * 1000; 026 private static final int DEFAULT_EXPIRY_TIMER_PERIOD = 60 * 1000; 027 028 public static final String CONFIG_KEY_VALIDITIY_PERIOD = "transientRepoPassword.validityPeriod"; 029 public static final String CONFIG_KEY_RENEWAL_PERIOD = "transientRepoPassword.renewalPeriod"; 030 public static final String CONFIG_KEY_EARLY_RENEWAL_PERIOD = "transientRepoPassword.earlyRenewalPeriod"; 031 public static final String CONFIG_KEY_EXPIRY_TIMER_PERIOD = "transientRepoPassword.expiryTimerPeriod"; 032 033 private int validityPeriod = Integer.MIN_VALUE; 034 private int renewalPeriod = Integer.MIN_VALUE; 035 private int earlyRenewalPeriod = Integer.MIN_VALUE; 036 private int expiryTimerPeriod = Integer.MIN_VALUE; 037 038 private static class TransientRepoPasswordManagerHolder { 039 public static final TransientRepoPasswordManager instance = new TransientRepoPasswordManager(); 040 } 041 042 protected TransientRepoPasswordManager() { } 043 044 public static TransientRepoPasswordManager getInstance() { 045 return TransientRepoPasswordManagerHolder.instance; 046 } 047 048 private final Map<UUID, Map<UUID, SortedSet<TransientRepoPassword>>> serverRepositoryId2ClientRepositoryId2AuthRepoPasswordSet = new HashMap<UUID, Map<UUID,SortedSet<TransientRepoPassword>>>(); 049 private final SortedSet<TransientRepoPassword> transientRepoPasswords = new TreeSet<TransientRepoPassword>(newestFirstAuthRepoPasswordComparator); 050 051 private final Timer timer = new Timer(); 052 private final TimerTask removeExpiredAuthRepoPasswordsTimerTask = new TimerTask() { 053 @Override 054 public void run() { 055 removeExpiredAuthRepoPasswords(); 056 } 057 }; 058 { 059 timer.schedule(removeExpiredAuthRepoPasswordsTimerTask, getExpiryTimerPeriod(), getExpiryTimerPeriod()); 060 } 061 062 public synchronized TransientRepoPassword getCurrentAuthRepoPassword(final UUID serverRepositoryId, final UUID clientRepositoryId) { 063 requireNonNull(serverRepositoryId, "serverRepositoryId"); 064 requireNonNull(clientRepositoryId, "clientRepositoryId"); 065 066 Map<UUID, SortedSet<TransientRepoPassword>> clientRepositoryId2AuthRepoPasswordSet = serverRepositoryId2ClientRepositoryId2AuthRepoPasswordSet.get(serverRepositoryId); 067 if (clientRepositoryId2AuthRepoPasswordSet == null) { 068 clientRepositoryId2AuthRepoPasswordSet = new HashMap<UUID, SortedSet<TransientRepoPassword>>(); 069 serverRepositoryId2ClientRepositoryId2AuthRepoPasswordSet.put(serverRepositoryId, clientRepositoryId2AuthRepoPasswordSet); 070 } 071 072 SortedSet<TransientRepoPassword> authRepoPasswordSet = clientRepositoryId2AuthRepoPasswordSet.get(clientRepositoryId); 073 if (authRepoPasswordSet == null) { 074 authRepoPasswordSet = new TreeSet<TransientRepoPassword>(newestFirstAuthRepoPasswordComparator); 075 clientRepositoryId2AuthRepoPasswordSet.put(clientRepositoryId, authRepoPasswordSet); 076 } 077 078 TransientRepoPassword transientRepoPassword = authRepoPasswordSet.isEmpty() ? null : authRepoPasswordSet.first(); 079 if (transientRepoPassword != null && isAfterRenewalDateOrInEarlyRenewalPeriod(transientRepoPassword)) 080 transientRepoPassword = null; 081 082 if (transientRepoPassword == null) { 083 transientRepoPassword = new TransientRepoPassword(serverRepositoryId, clientRepositoryId, createAuthToken()); 084 authRepoPasswordSet.add(transientRepoPassword); 085 transientRepoPasswords.add(transientRepoPassword); 086 } 087 return transientRepoPassword; 088 } 089 090 public synchronized boolean isPasswordValid(final UUID serverRepositoryId, final UUID clientRepositoryId, final char[] password) { 091 requireNonNull(serverRepositoryId, "serverRepositoryId"); 092 requireNonNull(clientRepositoryId, "clientRepositoryId"); 093 requireNonNull(password, "password"); 094 final Map<UUID, SortedSet<TransientRepoPassword>> clientRepositoryId2AuthRepoPasswordSet = serverRepositoryId2ClientRepositoryId2AuthRepoPasswordSet.get(serverRepositoryId); 095 if (clientRepositoryId2AuthRepoPasswordSet == null) 096 return false; 097 098 final SortedSet<TransientRepoPassword> authRepoPasswordSet = clientRepositoryId2AuthRepoPasswordSet.get(clientRepositoryId); 099 if (authRepoPasswordSet == null) 100 return false; 101 102 for (final TransientRepoPassword transientRepoPassword : authRepoPasswordSet) { 103 if (isExpired(transientRepoPassword)) // newest first => first expired means all following expired, too! 104 return false; 105 106 if (Arrays.equals(password, transientRepoPassword.getPassword())) 107 return true; 108 } 109 return false; 110 } 111 112 private synchronized void removeExpiredAuthRepoPasswords() { 113 while (!transientRepoPasswords.isEmpty()) { 114 final TransientRepoPassword oldestAuthRepoPassword = transientRepoPasswords.last(); 115 if (!isExpired(oldestAuthRepoPassword)) // newest first => last not yet expired means all previous not yet expired, either 116 break; 117 118 transientRepoPasswords.remove(oldestAuthRepoPassword); 119 final UUID serverRepositoryId = oldestAuthRepoPassword.getServerRepositoryId(); 120 final UUID clientRepositoryId = oldestAuthRepoPassword.getClientRepositoryId(); 121 122 final Map<UUID, SortedSet<TransientRepoPassword>> clientRepositoryId2AuthRepoPasswordSet = serverRepositoryId2ClientRepositoryId2AuthRepoPasswordSet.get(serverRepositoryId); 123 requireNonNull(clientRepositoryId2AuthRepoPasswordSet, "clientRepositoryId2AuthRepoPasswordSet"); 124 125 final SortedSet<TransientRepoPassword> authRepoPasswordSet = clientRepositoryId2AuthRepoPasswordSet.get(clientRepositoryId); 126 requireNonNull(authRepoPasswordSet, "authRepoPasswordSet"); 127 128 authRepoPasswordSet.remove(oldestAuthRepoPassword); 129 130 if (authRepoPasswordSet.isEmpty()) 131 clientRepositoryId2AuthRepoPasswordSet.remove(clientRepositoryId); 132 133 if (clientRepositoryId2AuthRepoPasswordSet.isEmpty()) 134 serverRepositoryId2ClientRepositoryId2AuthRepoPasswordSet.remove(serverRepositoryId); 135 } 136 } 137 138 protected int getValidityPeriod() { 139 if (validityPeriod == Integer.MIN_VALUE) { 140 validityPeriod = ConfigImpl.getInstance().getPropertyAsInt( 141 CONFIG_KEY_VALIDITIY_PERIOD, DEFAULT_VALIDITIY_PERIOD); 142 } 143 return validityPeriod; 144 } 145 146 protected int getRenewalPeriod() { 147 if (renewalPeriod == Integer.MIN_VALUE) { 148 renewalPeriod = ConfigImpl.getInstance().getPropertyAsInt( 149 CONFIG_KEY_RENEWAL_PERIOD, DEFAULT_RENEWAL_PERIOD); 150 } 151 return renewalPeriod; 152 } 153 154 protected int getEarlyRenewalPeriod() { 155 if (earlyRenewalPeriod == Integer.MIN_VALUE) { 156 earlyRenewalPeriod = ConfigImpl.getInstance().getPropertyAsInt( 157 CONFIG_KEY_EARLY_RENEWAL_PERIOD, DEFAULT_EARLY_RENEWAL_PERIOD); 158 } 159 return earlyRenewalPeriod; 160 } 161 162 protected int getExpiryTimerPeriod() { 163 if (expiryTimerPeriod == Integer.MIN_VALUE) { 164 expiryTimerPeriod = ConfigImpl.getInstance().getPropertyAsInt( 165 CONFIG_KEY_EXPIRY_TIMER_PERIOD, DEFAULT_EXPIRY_TIMER_PERIOD); 166 } 167 return expiryTimerPeriod; 168 } 169 170 private static final Comparator<TransientRepoPassword> newestFirstAuthRepoPasswordComparator = new Comparator<TransientRepoPassword>() { 171 @Override 172 public int compare(final TransientRepoPassword o1, final TransientRepoPassword o2) { 173 final Date expiryDate1 = o1.getAuthToken().getExpiryDateTime().toDate(); 174 final Date expiryDate2 = o2.getAuthToken().getExpiryDateTime().toDate(); 175 176 if (expiryDate1.before(expiryDate2)) 177 return +1; 178 179 if (expiryDate1.after(expiryDate2)) 180 return -1; 181 182 int result = o1.getServerRepositoryId().compareTo(o2.getServerRepositoryId()); 183 if (result != 0) 184 return result; 185 186 result = o1.getClientRepositoryId().compareTo(o2.getClientRepositoryId()); 187 return result; 188 } 189 }; 190 191 private boolean isAfterRenewalDateOrInEarlyRenewalPeriod(final TransientRepoPassword transientRepoPassword) { 192 requireNonNull(transientRepoPassword, "authRepoPassword"); 193 return System.currentTimeMillis() + getEarlyRenewalPeriod() > transientRepoPassword.getAuthToken().getRenewalDateTime().getMillis(); 194 } 195 196 private boolean isExpired(final TransientRepoPassword transientRepoPassword) { 197 requireNonNull(transientRepoPassword, "authRepoPassword"); 198 return System.currentTimeMillis() > transientRepoPassword.getAuthToken().getExpiryDateTime().getMillis(); 199 } 200 201 private AuthToken createAuthToken() { 202 final AuthToken authToken = new AuthToken(); 203 final Date expiryDate = new Date(System.currentTimeMillis() + getValidityPeriod()); 204 final Date renewalDate = new Date(System.currentTimeMillis() + getRenewalPeriod()); 205 authToken.setExpiryDateTime(new DateTime(expiryDate)); 206 authToken.setRenewalDateTime(new DateTime(renewalDate)); 207 authToken.setPassword(new String(PasswordUtil.createRandomPassword(40))); 208 authToken.makeUnmodifiable(); 209 return authToken; 210 } 211 212}