0
# Storage Backends
1
2
Pluggable storage system for associations and nonces with multiple backend implementations. The storage layer provides persistent storage for OpenID associations (shared secrets) and nonces (one-time tokens) with automatic cleanup of expired data.
3
4
## Capabilities
5
6
### Abstract Storage Interface
7
8
Base interface that all storage backends must implement for OpenID data persistence.
9
10
```python { .api }
11
class OpenIDStore:
12
"""Abstract base class for OpenID storage backends."""
13
14
def storeAssociation(self, server_url, association):
15
"""
16
Store an association between consumer and server.
17
18
Parameters:
19
- server_url: str, server URL for the association
20
- association: Association object containing shared secret
21
"""
22
23
def getAssociation(self, server_url, handle=None):
24
"""
25
Retrieve association for server URL and optional handle.
26
27
Parameters:
28
- server_url: str, server URL
29
- handle: str, association handle (None for most recent)
30
31
Returns:
32
Association object or None if not found/expired
33
"""
34
35
def removeAssociation(self, server_url, handle):
36
"""
37
Remove specific association from storage.
38
39
Parameters:
40
- server_url: str, server URL
41
- handle: str, association handle
42
43
Returns:
44
bool, True if association was removed
45
"""
46
47
def useNonce(self, server_url, timestamp, salt):
48
"""
49
Use a nonce, ensuring it can only be used once.
50
51
Parameters:
52
- server_url: str, server URL
53
- timestamp: int, nonce timestamp
54
- salt: str, nonce salt value
55
56
Returns:
57
bool, True if nonce was valid and unused, False if already used
58
"""
59
60
def cleanupNonces(self):
61
"""
62
Remove expired nonces from storage.
63
64
Returns:
65
int, number of nonces cleaned up
66
"""
67
68
def cleanupAssociations(self):
69
"""
70
Remove expired associations from storage.
71
72
Returns:
73
int, number of associations cleaned up
74
"""
75
76
def cleanup(self):
77
"""
78
Clean up both expired nonces and associations.
79
80
Returns:
81
int, total number of items cleaned up
82
"""
83
```
84
85
### File-Based Storage
86
87
File system storage implementation using directory structure for organization.
88
89
```python { .api }
90
class FileOpenIDStore(OpenIDStore):
91
"""File-based OpenID storage implementation."""
92
93
def __init__(self, directory):
94
"""
95
Initialize file store with directory path.
96
97
Parameters:
98
- directory: str, path to storage directory
99
"""
100
101
def storeAssociation(self, server_url, association):
102
"""Store association in file system."""
103
104
def getAssociation(self, server_url, handle=None):
105
"""Retrieve association from file system."""
106
107
def removeAssociation(self, server_url, handle):
108
"""Remove association file."""
109
110
def useNonce(self, server_url, timestamp, salt):
111
"""Check and mark nonce as used in file system."""
112
113
def cleanupNonces(self):
114
"""Remove expired nonce files."""
115
116
def cleanupAssociations(self):
117
"""Remove expired association files."""
118
```
119
120
### In-Memory Storage
121
122
Memory-based storage for testing and development environments.
123
124
```python { .api }
125
class MemoryStore(OpenIDStore):
126
"""In-memory OpenID storage implementation."""
127
128
def __init__(self):
129
"""Initialize memory store with empty collections."""
130
131
def storeAssociation(self, server_url, association):
132
"""Store association in memory."""
133
134
def getAssociation(self, server_url, handle=None):
135
"""Retrieve association from memory."""
136
137
def removeAssociation(self, server_url, handle):
138
"""Remove association from memory."""
139
140
def useNonce(self, server_url, timestamp, salt):
141
"""Check and mark nonce as used in memory."""
142
143
def cleanupNonces(self):
144
"""Remove expired nonces from memory."""
145
146
def cleanupAssociations(self):
147
"""Remove expired associations from memory."""
148
```
149
150
### SQL Database Storage
151
152
Base class for SQL database storage backends with common database operations.
153
154
```python { .api }
155
class SQLStore(OpenIDStore):
156
"""Base class for SQL database storage implementations."""
157
158
def __init__(self, connection, associations_table=None, nonces_table=None):
159
"""
160
Initialize SQL store with database connection.
161
162
Parameters:
163
- connection: database connection object
164
- associations_table: str, associations table name (default: 'oid_associations')
165
- nonces_table: str, nonces table name (default: 'oid_nonces')
166
"""
167
168
def createTables(self):
169
"""
170
Create required database tables if they don't exist.
171
"""
172
173
def storeAssociation(self, server_url, association):
174
"""Store association in database."""
175
176
def getAssociation(self, server_url, handle=None):
177
"""Retrieve association from database."""
178
179
def removeAssociation(self, server_url, handle):
180
"""Remove association from database."""
181
182
def useNonce(self, server_url, timestamp, salt):
183
"""Check and mark nonce as used in database."""
184
185
def cleanupNonces(self):
186
"""Remove expired nonces from database."""
187
188
def cleanupAssociations(self):
189
"""Remove expired associations from database."""
190
191
class MySQLStore(SQLStore):
192
"""MySQL-specific storage implementation."""
193
194
def __init__(self, connection, associations_table=None, nonces_table=None):
195
"""Initialize MySQL store with MySQL connection."""
196
197
class PostgreSQLStore(SQLStore):
198
"""PostgreSQL-specific storage implementation."""
199
200
def __init__(self, connection, associations_table=None, nonces_table=None):
201
"""Initialize PostgreSQL store with psycopg2 connection."""
202
203
class SQLiteStore(SQLStore):
204
"""SQLite-specific storage implementation."""
205
206
def __init__(self, connection, associations_table=None, nonces_table=None):
207
"""Initialize SQLite store with sqlite3 connection."""
208
```
209
210
### Nonce Management
211
212
Utilities for handling nonces (one-time tokens) with timestamp validation.
213
214
```python { .api }
215
def mkNonce():
216
"""
217
Generate a new nonce value.
218
219
Returns:
220
str, randomly generated nonce
221
"""
222
223
def split_nonce(nonce_string):
224
"""
225
Split nonce string into timestamp and salt components.
226
227
Parameters:
228
- nonce_string: str, nonce in format 'timestamp-salt'
229
230
Returns:
231
tuple, (timestamp, salt) or None if invalid format
232
"""
233
234
def checkTimestamp(nonce_string, allowed_skew=None, now=None):
235
"""
236
Check if nonce timestamp is within allowed time window.
237
238
Parameters:
239
- nonce_string: str, nonce string
240
- allowed_skew: int, allowed time skew in seconds (default: 5 hours)
241
- now: int, current timestamp (default: current time)
242
243
Returns:
244
bool, True if timestamp is valid
245
"""
246
```
247
248
## Usage Examples
249
250
### File Store Setup
251
252
```python
253
from openid.store.filestore import FileOpenIDStore
254
import os
255
256
# Create file store
257
store_dir = '/tmp/openid_store'
258
os.makedirs(store_dir, exist_ok=True)
259
store = FileOpenIDStore(store_dir)
260
261
# Use with consumer
262
from openid.consumer import consumer
263
openid_consumer = consumer.Consumer({}, store)
264
265
# Use with server
266
from openid.server import server
267
openid_server = server.Server(store, op_endpoint="https://myop.com/openid")
268
```
269
270
### SQL Store Setup
271
272
```python
273
from openid.store.sqlstore import SQLiteStore
274
import sqlite3
275
276
# Create SQLite store
277
connection = sqlite3.connect('/tmp/openid.db')
278
store = SQLiteStore(connection)
279
store.createTables()
280
281
# Use with consumer or server
282
openid_consumer = consumer.Consumer({}, store)
283
```
284
285
### MySQL Store Setup
286
287
```python
288
from openid.store.sqlstore import MySQLStore
289
import mysql.connector
290
291
# Create MySQL store (requires mysql-connector-python)
292
connection = mysql.connector.connect(
293
host='localhost',
294
user='openid_user',
295
password='password',
296
database='openid_db'
297
)
298
store = MySQLStore(connection)
299
store.createTables()
300
```
301
302
### PostgreSQL Store Setup
303
304
```python
305
from openid.store.sqlstore import PostgreSQLStore
306
import psycopg2
307
308
# Create PostgreSQL store (requires psycopg2)
309
connection = psycopg2.connect(
310
host='localhost',
311
user='openid_user',
312
password='password',
313
database='openid_db'
314
)
315
store = PostgreSQLStore(connection)
316
store.createTables()
317
```
318
319
### Memory Store for Testing
320
321
```python
322
from openid.store.memstore import MemoryStore
323
324
# Create memory store (for testing only)
325
store = MemoryStore()
326
327
# Use for unit tests
328
def test_consumer():
329
test_consumer = consumer.Consumer({}, store)
330
# ... test code
331
```
332
333
### Custom Store Implementation
334
335
```python
336
from openid.store.interface import OpenIDStore
337
from openid.association import Association
338
339
class CustomStore(OpenIDStore):
340
"""Custom storage backend implementation."""
341
342
def __init__(self, custom_backend):
343
self.backend = custom_backend
344
345
def storeAssociation(self, server_url, association):
346
# Store in custom backend
347
self.backend.set(
348
f"assoc:{server_url}:{association.handle}",
349
association.serialize(),
350
ttl=association.expiresIn
351
)
352
353
def getAssociation(self, server_url, handle=None):
354
if handle:
355
key = f"assoc:{server_url}:{handle}"
356
else:
357
# Get most recent association
358
key = self.backend.get_latest_key(f"assoc:{server_url}:*")
359
360
data = self.backend.get(key)
361
if data:
362
return Association.deserialize(data)
363
return None
364
365
# Implement other required methods...
366
```
367
368
### Store Cleanup
369
370
```python
371
# Regular cleanup of expired data
372
def cleanup_openid_store(store):
373
"""Perform regular cleanup of OpenID store."""
374
nonces_cleaned = store.cleanupNonces()
375
associations_cleaned = store.cleanupAssociations()
376
377
print(f"Cleaned up {nonces_cleaned} nonces and {associations_cleaned} associations")
378
379
# Schedule cleanup (example with APScheduler)
380
from apscheduler.schedulers.background import BackgroundScheduler
381
382
scheduler = BackgroundScheduler()
383
scheduler.add_job(
384
cleanup_openid_store,
385
'interval',
386
hours=1, # Run hourly
387
args=[store]
388
)
389
scheduler.start()
390
```
391
392
## Types
393
394
```python { .api }
395
# Default table names
396
DEFAULT_ASSOCIATIONS_TABLE = 'oid_associations'
397
DEFAULT_NONCES_TABLE = 'oid_nonces'
398
399
# Nonce configuration
400
NONCE_LENGTH = 8 # Length of random nonce component
401
SKEW = 5 * 60 * 60 # Default allowed time skew (5 hours)
402
403
# SQL table schemas (for reference)
404
ASSOCIATIONS_TABLE_SCHEMA = """
405
CREATE TABLE oid_associations (
406
server_url VARCHAR(2047) NOT NULL,
407
handle VARCHAR(255) NOT NULL,
408
secret BLOB NOT NULL,
409
issued INTEGER NOT NULL,
410
lifetime INTEGER NOT NULL,
411
assoc_type VARCHAR(64) NOT NULL,
412
PRIMARY KEY (server_url, handle)
413
);
414
"""
415
416
NONCES_TABLE_SCHEMA = """
417
CREATE TABLE oid_nonces (
418
server_url VARCHAR(2047) NOT NULL,
419
timestamp INTEGER NOT NULL,
420
salt CHAR(40) NOT NULL,
421
PRIMARY KEY (server_url, timestamp, salt)
422
);
423
"""
424
```