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}