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