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

extensions.mddocs/

0

# OpenID Extensions

1

2

Implementation of standard OpenID extensions including Simple Registration (SREG) for basic profile data and Attribute Exchange (AX) for flexible attribute sharing between consumers and servers.

3

4

## Capabilities

5

6

### Simple Registration Extension (SREG)

7

8

Standard extension for requesting and sharing basic profile information between OpenID consumers and servers.

9

10

```python { .api }

11

class SRegRequest:

12

"""Simple Registration extension request."""

13

14

def __init__(self, required=None, optional=None, policy_url=None, sreg_ns_uri=ns_uri):

15

"""

16

Initialize SREG request.

17

18

Parameters:

19

- required: list, required field names

20

- optional: list, optional field names

21

- policy_url: str, privacy policy URL

22

- sreg_ns_uri: str, SREG namespace URI

23

"""

24

25

@classmethod

26

def fromOpenIDRequest(cls, request):

27

"""

28

Create SREG request from OpenID authentication request.

29

30

Parameters:

31

- request: AuthRequest object

32

33

Returns:

34

SRegRequest object or None if no SREG data

35

"""

36

37

def parseExtensionArgs(self, args, strict=False):

38

"""

39

Parse extension arguments from OpenID message.

40

41

Parameters:

42

- args: dict, extension arguments

43

- strict: bool, whether to enforce strict validation

44

"""

45

46

def requestField(self, field_name, required=False, strict=False):

47

"""

48

Request a single profile field.

49

50

Parameters:

51

- field_name: str, field name (e.g., 'nickname', 'email')

52

- required: bool, whether field is required

53

- strict: bool, whether to enforce strict validation

54

"""

55

56

def requestFields(self, field_names, required=False, strict=False):

57

"""

58

Request multiple profile fields.

59

60

Parameters:

61

- field_names: list, field names to request

62

- required: bool, whether fields are required

63

- strict: bool, whether to enforce strict validation

64

"""

65

66

def getExtensionArgs(self):

67

"""

68

Get extension arguments for OpenID message.

69

70

Returns:

71

dict, extension arguments

72

"""

73

74

def allRequestedFields(self):

75

"""

76

Get all requested fields (required and optional).

77

78

Returns:

79

list, all requested field names

80

"""

81

82

def wereFieldsRequested(self):

83

"""

84

Check if any fields were requested.

85

86

Returns:

87

bool, True if fields were requested

88

"""

89

90

class SRegResponse:

91

"""Simple Registration extension response."""

92

93

def __init__(self, data=None, sreg_ns_uri=ns_uri):

94

"""

95

Initialize SREG response.

96

97

Parameters:

98

- data: dict, profile data

99

- sreg_ns_uri: str, SREG namespace URI

100

"""

101

102

@classmethod

103

def extractResponse(cls, request, data):

104

"""

105

Extract SREG response data matching request.

106

107

Parameters:

108

- request: SRegRequest object

109

- data: dict, available profile data

110

111

Returns:

112

SRegResponse object with matching fields

113

"""

114

115

@classmethod

116

def fromSuccessResponse(cls, success_response, signed_only=True):

117

"""

118

Create SREG response from successful OpenID response.

119

120

Parameters:

121

- success_response: SuccessResponse object

122

- signed_only: bool, whether to only include signed fields

123

124

Returns:

125

SRegResponse object or None if no SREG data

126

"""

127

128

def getExtensionArgs(self):

129

"""

130

Get extension arguments for OpenID message.

131

132

Returns:

133

dict, extension arguments

134

"""

135

136

def get(self, field_name, default=None):

137

"""

138

Get profile field value.

139

140

Parameters:

141

- field_name: str, field name

142

- default: default value if field not present

143

144

Returns:

145

str or default, field value

146

"""

147

148

def items(self):

149

"""

150

Get all profile data as key-value pairs.

151

152

Returns:

153

list, [(field_name, value), ...] tuples

154

"""

155

156

def keys(self):

157

"""

158

Get all profile field names.

159

160

Returns:

161

list, field names

162

"""

163

164

def values(self):

165

"""

166

Get all profile field values.

167

168

Returns:

169

list, field values

170

"""

171

```

172

173

### Attribute Exchange Extension (AX)

174

175

Flexible extension for exchanging structured attribute data with support for multiple values and custom attribute types.

176

177

```python { .api }

178

class AttrInfo:

179

"""Attribute information for AX requests."""

180

181

def __init__(self, type_uri, count=1, required=False, alias=None):

182

"""

183

Initialize attribute information.

184

185

Parameters:

186

- type_uri: str, attribute type URI

187

- count: int or 'unlimited', number of values requested

188

- required: bool, whether attribute is required

189

- alias: str, attribute alias (auto-generated if None)

190

"""

191

192

def wantsUnlimitedValues(self):

193

"""

194

Check if unlimited values are requested.

195

196

Returns:

197

bool, True if unlimited values wanted

198

"""

199

200

class FetchRequest:

201

"""AX fetch request for retrieving attributes."""

202

203

def __init__(self, update_url=None):

204

"""

205

Initialize fetch request.

206

207

Parameters:

208

- update_url: str, URL for attribute updates

209

"""

210

211

def add(self, attribute):

212

"""

213

Add attribute to fetch request.

214

215

Parameters:

216

- attribute: AttrInfo object

217

"""

218

219

@classmethod

220

def fromOpenIDRequest(cls, openid_request):

221

"""

222

Create fetch request from OpenID authentication request.

223

224

Parameters:

225

- openid_request: AuthRequest object

226

227

Returns:

228

FetchRequest object or None if no AX data

229

"""

230

231

def parseExtensionArgs(self, ax_args):

232

"""

233

Parse AX extension arguments.

234

235

Parameters:

236

- ax_args: dict, AX extension arguments

237

"""

238

239

def getExtensionArgs(self):

240

"""

241

Get extension arguments for OpenID message.

242

243

Returns:

244

dict, extension arguments

245

"""

246

247

def getRequiredAttrs(self):

248

"""

249

Get required attributes.

250

251

Returns:

252

list, AttrInfo objects for required attributes

253

"""

254

255

def iterAttrs(self):

256

"""

257

Iterate over all requested attributes.

258

259

Returns:

260

iterator, AttrInfo objects

261

"""

262

263

class FetchResponse:

264

"""AX fetch response with attribute data."""

265

266

def __init__(self, request=None, update_url=None):

267

"""

268

Initialize fetch response.

269

270

Parameters:

271

- request: FetchRequest object (optional)

272

- update_url: str, URL for attribute updates

273

"""

274

275

@classmethod

276

def fromSuccessResponse(cls, success_response, signed=True):

277

"""

278

Create fetch response from successful OpenID response.

279

280

Parameters:

281

- success_response: SuccessResponse object

282

- signed: bool, whether to only include signed attributes

283

284

Returns:

285

FetchResponse object or None if no AX data

286

"""

287

288

def getExtensionArgs(self):

289

"""

290

Get extension arguments for OpenID message.

291

292

Returns:

293

dict, extension arguments

294

"""

295

296

def parseExtensionArgs(self, ax_args):

297

"""

298

Parse AX extension arguments.

299

300

Parameters:

301

- ax_args: dict, AX extension arguments

302

"""

303

304

class StoreRequest:

305

"""AX store request for sending attributes to server."""

306

307

def __init__(self, aliases=None):

308

"""

309

Initialize store request.

310

311

Parameters:

312

- aliases: dict, attribute type URI to alias mapping

313

"""

314

315

def getExtensionArgs(self):

316

"""

317

Get extension arguments for OpenID message.

318

319

Returns:

320

dict, extension arguments

321

"""

322

323

class StoreResponse:

324

"""AX store response indicating success or failure."""

325

326

def __init__(self, succeeded=True, error_message=None):

327

"""

328

Initialize store response.

329

330

Parameters:

331

- succeeded: bool, whether store operation succeeded

332

- error_message: str, error message if failed

333

"""

334

335

def succeeded(self):

336

"""

337

Check if store operation succeeded.

338

339

Returns:

340

bool, True if succeeded

341

"""

342

343

def getExtensionArgs(self):

344

"""

345

Get extension arguments for OpenID message.

346

347

Returns:

348

dict, extension arguments

349

"""

350

```

351

352

### AX Base Classes

353

354

Base classes providing common functionality for AX message handling.

355

356

```python { .api }

357

class AXMessage:

358

"""Abstract base class for AX messages."""

359

360

def getExtensionArgs(self):

361

"""Get extension arguments."""

362

363

class AXKeyValueMessage(AXMessage):

364

"""Base class for AX messages with key-value data."""

365

366

def addValue(self, type_uri, value):

367

"""

368

Add single value for attribute type.

369

370

Parameters:

371

- type_uri: str, attribute type URI

372

- value: str, attribute value

373

"""

374

375

def setValues(self, type_uri, values):

376

"""

377

Set multiple values for attribute type.

378

379

Parameters:

380

- type_uri: str, attribute type URI

381

- values: list, attribute values

382

"""

383

384

def getSingle(self, type_uri, default=None):

385

"""

386

Get single value for attribute type.

387

388

Parameters:

389

- type_uri: str, attribute type URI

390

- default: default value if not found

391

392

Returns:

393

str or default, attribute value

394

"""

395

396

def get(self, type_uri):

397

"""

398

Get all values for attribute type.

399

400

Parameters:

401

- type_uri: str, attribute type URI

402

403

Returns:

404

list, attribute values

405

"""

406

407

def count(self, type_uri):

408

"""

409

Count values for attribute type.

410

411

Parameters:

412

- type_uri: str, attribute type URI

413

414

Returns:

415

int, number of values

416

"""

417

```

418

419

### Extension Utilities

420

421

Utility functions for working with OpenID extensions.

422

423

```python { .api }

424

def checkFieldName(field_name):

425

"""

426

Validate SREG field name.

427

428

Parameters:

429

- field_name: str, field name to validate

430

431

Raises:

432

ValueError: if field name is invalid

433

"""

434

435

def getSRegNS(message):

436

"""

437

Get SREG namespace URI from OpenID message.

438

439

Parameters:

440

- message: Message object

441

442

Returns:

443

str, SREG namespace URI or None

444

"""

445

446

def supportsSReg(endpoint):

447

"""

448

Check if endpoint supports SREG extension.

449

450

Parameters:

451

- endpoint: OpenIDServiceEndpoint object

452

453

Returns:

454

bool, True if SREG is supported

455

"""

456

457

def checkAlias(alias):

458

"""

459

Validate AX attribute alias.

460

461

Parameters:

462

- alias: str, alias to validate

463

464

Raises:

465

ValueError: if alias is invalid

466

"""

467

468

def toTypeURIs(namespace_map, alias_list_s):

469

"""

470

Convert comma-separated alias list to type URIs.

471

472

Parameters:

473

- namespace_map: dict, alias to type URI mapping

474

- alias_list_s: str, comma-separated alias list

475

476

Returns:

477

list, type URIs

478

"""

479

```

480

481

## Usage Examples

482

483

### SREG Consumer Usage

484

485

```python

486

from openid.extensions import sreg

487

from openid.consumer import consumer

488

489

# Create consumer and start authentication

490

openid_consumer = consumer.Consumer({}, store)

491

auth_request = openid_consumer.begin(user_url)

492

493

# Add SREG request

494

sreg_request = sreg.SRegRequest(

495

required=['nickname', 'email'], # Required fields

496

optional=['fullname', 'dob'], # Optional fields

497

policy_url="https://mysite.com/privacy"

498

)

499

auth_request.addExtension(sreg_request)

500

501

# Redirect user to identity provider

502

redirect_url = auth_request.redirectURL(realm, return_to)

503

504

# Handle response

505

response = openid_consumer.complete(query, current_url)

506

if response.status == consumer.SUCCESS:

507

sreg_response = sreg.SRegResponse.fromSuccessResponse(response)

508

if sreg_response:

509

nickname = sreg_response.get('nickname')

510

email = sreg_response.get('email')

511

fullname = sreg_response.get('fullname')

512

513

# Use profile data

514

create_user_account(nickname, email, fullname)

515

```

516

517

### SREG Server Usage

518

519

```python

520

from openid.extensions import sreg

521

522

def handle_checkid_request(openid_request):

523

# Check for SREG request

524

sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)

525

526

if sreg_request:

527

# Get user profile data

528

user_profile = get_user_profile(user_id)

529

530

# Create SREG response with requested fields

531

sreg_response = sreg.SRegResponse.extractResponse(

532

sreg_request,

533

user_profile

534

)

535

536

# Create positive OpenID response

537

openid_response = openid_request.answer(allow=True, ...)

538

openid_response.addExtension(sreg_response)

539

540

return openid_response

541

```

542

543

### AX Consumer Usage

544

545

```python

546

from openid.extensions import ax

547

548

# Create AX fetch request

549

ax_request = ax.FetchRequest()

550

551

# Add specific attributes

552

ax_request.add(ax.AttrInfo(

553

'http://schema.openid.net/contact/email',

554

required=True,

555

alias='email'

556

))

557

ax_request.add(ax.AttrInfo(

558

'http://schema.openid.net/namePerson/friendly',

559

required=False,

560

alias='nickname'

561

))

562

ax_request.add(ax.AttrInfo(

563

'http://schema.openid.net/contact/web/homepage',

564

count='unlimited', # Allow multiple URLs

565

alias='website'

566

))

567

568

# Add to authentication request

569

auth_request.addExtension(ax_request)

570

571

# Handle response

572

response = openid_consumer.complete(query, current_url)

573

if response.status == consumer.SUCCESS:

574

ax_response = ax.FetchResponse.fromSuccessResponse(response)

575

if ax_response:

576

email = ax_response.getSingle('http://schema.openid.net/contact/email')

577

nickname = ax_response.getSingle('http://schema.openid.net/namePerson/friendly')

578

websites = ax_response.get('http://schema.openid.net/contact/web/homepage')

579

```

580

581

### AX Server Usage

582

583

```python

584

from openid.extensions import ax

585

586

def handle_ax_fetch_request(openid_request):

587

ax_request = ax.FetchRequest.fromOpenIDRequest(openid_request)

588

589

if ax_request:

590

ax_response = ax.FetchResponse()

591

592

# Process each requested attribute

593

for attr_info in ax_request.iterAttrs():

594

if attr_info.type_uri == 'http://schema.openid.net/contact/email':

595

if user_allows_email_sharing():

596

ax_response.addValue(attr_info.type_uri, user.email)

597

598

elif attr_info.type_uri == 'http://schema.openid.net/namePerson/friendly':

599

ax_response.addValue(attr_info.type_uri, user.nickname)

600

601

elif attr_info.type_uri == 'http://schema.openid.net/contact/web/homepage':

602

# Multiple values supported

603

for website in user.websites:

604

ax_response.addValue(attr_info.type_uri, website)

605

606

# Add to OpenID response

607

openid_response = openid_request.answer(allow=True, ...)

608

openid_response.addExtension(ax_response)

609

610

return openid_response

611

```

612

613

## Types

614

615

```python { .api }

616

# SREG namespace URIs

617

ns_uri_1_0 = 'http://openid.net/sreg/1.0'

618

ns_uri_1_1 = 'http://openid.net/extensions/sreg/1.1'

619

ns_uri = ns_uri_1_1 # Preferred namespace

620

621

# SREG data fields

622

data_fields = {

623

'nickname': 'Any UTF-8 string that the End User wants to use as a nickname.',

624

'email': 'The email address of the End User as specified in section 3.4.1 of [RFC2822]',

625

'fullname': 'UTF-8 string free text representation of the End User\'s full name.',

626

'dob': 'The End User\'s date of birth as YYYY-MM-DD.',

627

'gender': 'The End User\'s gender, "M" for male, "F" for female.',

628

'postcode': 'UTF-8 string free text that SHOULD conform to the End User\'s country\'s postal system.',

629

'country': 'The End User\'s country of residence as specified by ISO3166.',

630

'language': 'End User\'s preferred language as specified by ISO639.',

631

'timezone': 'ASCII string from TimeZone database'

632

}

633

634

# AX constants

635

UNLIMITED_VALUES = "unlimited"

636

MINIMUM_SUPPORTED_ALIAS_LENGTH = 32

637

638

# Common AX attribute type URIs

639

AX_SCHEMA_ATTRS = {

640

'email': 'http://schema.openid.net/contact/email',

641

'nickname': 'http://schema.openid.net/namePerson/friendly',

642

'fullname': 'http://schema.openid.net/namePerson',

643

'first_name': 'http://schema.openid.net/namePerson/first',

644

'last_name': 'http://schema.openid.net/namePerson/last',

645

'website': 'http://schema.openid.net/contact/web/homepage',

646

'image': 'http://schema.openid.net/media/image/aspect11',

647

'country': 'http://schema.openid.net/contact/country/home',

648

'language': 'http://schema.openid.net/pref/language',

649

'timezone': 'http://schema.openid.net/pref/timezone'

650

}

651

652

# Exception types

653

class SRegNamespaceError(Exception):

654

"""SREG namespace error."""

655

656

class AXError(Exception):

657

"""AX extension error."""

658

659

class NotAXMessage(Exception):

660

"""Message is not an AX message."""

661

```