1"""A simple store using only in-process memory."""
2
3from openid.store import nonce
4
5import copy
6import time
7
8class ServerAssocs(object):
9    def __init__(self):
10        self.assocs = {}
11
12    def set(self, assoc):
13        self.assocs[assoc.handle] = assoc
14
15    def get(self, handle):
16        return self.assocs.get(handle)
17
18    def remove(self, handle):
19        try:
20            del self.assocs[handle]
21        except KeyError:
22            return False
23        else:
24            return True
25
26    def best(self):
27        """Returns association with the oldest issued date.
28
29        or None if there are no associations.
30        """
31        best = None
32        for assoc in self.assocs.values():
33            if best is None or best.issued < assoc.issued:
34                best = assoc
35        return best
36
37    def cleanup(self):
38        """Remove expired associations.
39
40        @return: tuple of (removed associations, remaining associations)
41        """
42        remove = []
43        for handle, assoc in self.assocs.iteritems():
44            if assoc.getExpiresIn() == 0:
45                remove.append(handle)
46        for handle in remove:
47            del self.assocs[handle]
48        return len(remove), len(self.assocs)
49
50
51
52class MemoryStore(object):
53    """In-process memory store.
54
55    Use for single long-running processes.  No persistence supplied.
56    """
57    def __init__(self):
58        self.server_assocs = {}
59        self.nonces = {}
60
61    def _getServerAssocs(self, server_url):
62        try:
63            return self.server_assocs[server_url]
64        except KeyError:
65            assocs = self.server_assocs[server_url] = ServerAssocs()
66            return assocs
67
68    def storeAssociation(self, server_url, assoc):
69        assocs = self._getServerAssocs(server_url)
70        assocs.set(copy.deepcopy(assoc))
71
72    def getAssociation(self, server_url, handle=None):
73        assocs = self._getServerAssocs(server_url)
74        if handle is None:
75            return assocs.best()
76        else:
77            return assocs.get(handle)
78
79    def removeAssociation(self, server_url, handle):
80        assocs = self._getServerAssocs(server_url)
81        return assocs.remove(handle)
82
83    def useNonce(self, server_url, timestamp, salt):
84        if abs(timestamp - time.time()) > nonce.SKEW:
85            return False
86
87        anonce = (str(server_url), int(timestamp), str(salt))
88        if anonce in self.nonces:
89            return False
90        else:
91            self.nonces[anonce] = None
92            return True
93
94    def cleanupNonces(self):
95        now = time.time()
96        expired = []
97        for anonce in self.nonces.iterkeys():
98            if abs(anonce[1] - now) > nonce.SKEW:
99                # removing items while iterating over the set could be bad.
100                expired.append(anonce)
101
102        for anonce in expired:
103            del self.nonces[anonce]
104        return len(expired)
105
106    def cleanupAssociations(self):
107        remove_urls = []
108        removed_assocs = 0
109        for server_url, assocs in self.server_assocs.iteritems():
110            removed, remaining = assocs.cleanup()
111            removed_assocs += removed
112            if not remaining:
113                remove_urls.append(server_url)
114
115        # Remove entries from server_assocs that had none remaining.
116        for server_url in remove_urls:
117            del self.server_assocs[server_url]
118        return removed_assocs
119
120    def __eq__(self, other):
121        return ((self.server_assocs == other.server_assocs) and
122                (self.nonces == other.nonces))
123
124    def __ne__(self, other):
125        return not (self == other)
126