001package co.codewizards.cloudstore.rest.server.auth;
002
003import static co.codewizards.cloudstore.core.util.Util.*;
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.Config;
018import co.codewizards.cloudstore.core.dto.DateTime;
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(UUID serverRepositoryId, UUID clientRepositoryId) {
062                assertNotNull("serverRepositoryId", serverRepositoryId);
063                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(UUID serverRepositoryId, UUID clientRepositoryId, char[] password) {
090                assertNotNull("serverRepositoryId", serverRepositoryId);
091                assertNotNull("clientRepositoryId", clientRepositoryId);
092                assertNotNull("password", password);
093                Map<UUID, SortedSet<TransientRepoPassword>> clientRepositoryId2AuthRepoPasswordSet = serverRepositoryId2ClientRepositoryId2AuthRepoPasswordSet.get(serverRepositoryId);
094                if (clientRepositoryId2AuthRepoPasswordSet == null)
095                        return false;
096
097                SortedSet<TransientRepoPassword> authRepoPasswordSet = clientRepositoryId2AuthRepoPasswordSet.get(clientRepositoryId);
098                if (authRepoPasswordSet == null)
099                        return false;
100
101                for (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                        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                        UUID serverRepositoryId = oldestAuthRepoPassword.getServerRepositoryId();
119                        UUID clientRepositoryId = oldestAuthRepoPassword.getClientRepositoryId();
120
121                        Map<UUID, SortedSet<TransientRepoPassword>> clientRepositoryId2AuthRepoPasswordSet = serverRepositoryId2ClientRepositoryId2AuthRepoPasswordSet.get(serverRepositoryId);
122                        assertNotNull("clientRepositoryId2AuthRepoPasswordSet", clientRepositoryId2AuthRepoPasswordSet);
123
124                        SortedSet<TransientRepoPassword> authRepoPasswordSet = clientRepositoryId2AuthRepoPasswordSet.get(clientRepositoryId);
125                        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 = Config.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 = Config.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 = Config.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 = Config.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(TransientRepoPassword o1, TransientRepoPassword o2) {
172                        Date expiryDate1 = o1.getAuthToken().getExpiryDateTime().toDate();
173                        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(TransientRepoPassword transientRepoPassword) {
191                assertNotNull("authRepoPassword", transientRepoPassword);
192                return System.currentTimeMillis() + getEarlyRenewalPeriod() > transientRepoPassword.getAuthToken().getRenewalDateTime().getMillis();
193        }
194
195        private boolean isExpired(TransientRepoPassword transientRepoPassword) {
196                assertNotNull("authRepoPassword", transientRepoPassword);
197                return System.currentTimeMillis() > transientRepoPassword.getAuthToken().getExpiryDateTime().getMillis();
198        }
199
200        private AuthToken createAuthToken() {
201                AuthToken authToken = new AuthToken();
202                Date expiryDate = new Date(System.currentTimeMillis() + getValidityPeriod());
203                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}