or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

association-management.mdconsumer-auth.mddiscovery.mdextensions.mdindex.mdmessage-processing.mdserver-implementation.mdstorage-backends.mdutilities.md

storage-backends.mddocs/

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

```