0
# Server Implementation
1
2
Complete OpenID server functionality for identity providers. The server handles association requests, authentication requests, and verification with support for multiple session types and response encoding formats.
3
4
## Capabilities
5
6
### Server Initialization
7
8
Initialize an OpenID server with storage backend and endpoint configuration.
9
10
```python { .api }
11
class Server:
12
def __init__(self, store, op_endpoint=None, signatoryClass=Signatory, encoderClass=SigningEncoder, decoderClass=Decoder):
13
"""
14
Initialize OpenID server.
15
16
Parameters:
17
- store: OpenIDStore instance for persistent storage
18
- op_endpoint: str, server endpoint URL
19
- signatoryClass: class, signatory implementation (default: Signatory)
20
- encoderClass: class, encoder implementation (default: SigningEncoder)
21
- decoderClass: class, decoder implementation (default: Decoder)
22
"""
23
24
def decodeRequest(self, query):
25
"""
26
Decode incoming OpenID request from query parameters.
27
28
Parameters:
29
- query: dict, query parameters from HTTP request
30
31
Returns:
32
OpenIDRequest subclass or None if invalid
33
"""
34
35
def encodeResponse(self, response):
36
"""
37
Encode OpenID response for HTTP transmission.
38
39
Parameters:
40
- response: OpenIDResponse object
41
42
Returns:
43
WebResponse object with HTTP status, headers, and body
44
"""
45
46
def handleRequest(self, request):
47
"""
48
Handle decoded OpenID request and generate appropriate response.
49
50
Parameters:
51
- request: OpenIDRequest object
52
53
Returns:
54
OpenIDResponse object
55
"""
56
57
def openid_check_authentication(self, request):
58
"""
59
Handle check_authentication request for verifying signatures.
60
61
Parameters:
62
- request: CheckAuthRequest object
63
64
Returns:
65
OpenIDResponse with verification result
66
"""
67
68
def openid_associate(self, request):
69
"""
70
Handle associate request for establishing shared secrets.
71
72
Parameters:
73
- request: AssociateRequest object
74
75
Returns:
76
OpenIDResponse with association data or error
77
"""
78
```
79
80
### Base Request Classes
81
82
Handle the base OpenID request types and common functionality.
83
84
```python { .api }
85
class OpenIDRequest:
86
"""Base class for all OpenID requests.
87
88
Represents an incoming OpenID request with common attributes and methods.
89
"""
90
91
mode = None # The openid.mode parameter value
92
93
def __init__(self):
94
"""
95
Initialize base OpenID request.
96
97
The mode attribute should be set by subclasses to indicate
98
the specific OpenID operation being requested.
99
"""
100
```
101
102
### Authentication Requests
103
104
Handle user authentication requests with trust root validation and response generation.
105
106
```python { .api }
107
class CheckIDRequest:
108
"""OpenID authentication request (checkid_setup or checkid_immediate)."""
109
110
@classmethod
111
def fromMessage(cls, message, op_endpoint):
112
"""
113
Create CheckIDRequest from OpenID message.
114
115
Parameters:
116
- message: Message object
117
- op_endpoint: str, server endpoint URL
118
119
Returns:
120
CheckIDRequest object
121
"""
122
123
def answer(self, allow, server_url=None, identity=None, claimed_id=None):
124
"""
125
Generate response to authentication request.
126
127
Parameters:
128
- allow: bool, whether to allow authentication
129
- server_url: str, server URL for response
130
- identity: str, user's identity URL
131
- claimed_id: str, user's claimed identifier
132
133
Returns:
134
OpenIDResponse object
135
"""
136
137
def encodeToURL(self, server_url):
138
"""
139
Encode request as URL for redirecting user.
140
141
Parameters:
142
- server_url: str, server base URL
143
144
Returns:
145
str, encoded URL
146
"""
147
148
def getCancelURL(self):
149
"""
150
Get URL for user cancellation.
151
152
Returns:
153
str, cancel URL
154
"""
155
156
def idSelect(self):
157
"""
158
Check if request uses identifier select mode.
159
160
Returns:
161
bool, True if identifier select
162
"""
163
164
def trustRootValid(self):
165
"""
166
Validate the trust root against return_to URL.
167
168
Returns:
169
bool, True if trust root is valid
170
"""
171
172
def returnToVerified(self):
173
"""
174
Verify the return_to URL is valid and matches trust root.
175
176
Returns:
177
bool, True if return_to is verified
178
"""
179
180
def getClaimedID(self):
181
"""
182
Get the claimed identifier from this request.
183
184
Returns:
185
str, the claimed identifier (OpenID 2.0) or identity (OpenID 1.x)
186
"""
187
188
def getTrustRoot(self):
189
"""
190
Get the trust root (realm) from this request.
191
192
Returns:
193
str, the trust root URL that identifies the requesting party
194
"""
195
```
196
197
### Association Requests
198
199
Handle association establishment for shared secret creation between consumer and server.
200
201
```python { .api }
202
class AssociateRequest(OpenIDRequest):
203
"""OpenID association request.
204
205
A request to establish an association between consumer and server.
206
207
Attributes:
208
- mode: "associate"
209
- assoc_type: str, type of association ("HMAC-SHA1" or "HMAC-SHA256")
210
- session: ServerSession, session handler for this association type
211
- session_classes: dict, mapping of session types to handler classes
212
"""
213
214
mode = "associate"
215
session_classes = {
216
'no-encryption': 'PlainTextServerSession',
217
'DH-SHA1': 'DiffieHellmanSHA1ServerSession',
218
'DH-SHA256': 'DiffieHellmanSHA256ServerSession',
219
}
220
221
def __init__(self, session, assoc_type):
222
"""
223
Construct AssociateRequest.
224
225
Parameters:
226
- session: ServerSession object for handling this association type
227
- assoc_type: str, association type ("HMAC-SHA1" or "HMAC-SHA256")
228
"""
229
230
@classmethod
231
def fromMessage(cls, message, op_endpoint=None):
232
"""
233
Create AssociateRequest from OpenID message.
234
235
Parameters:
236
- message: Message object
237
- op_endpoint: str, server endpoint URL
238
239
Returns:
240
AssociateRequest object
241
"""
242
243
def answer(self, assoc):
244
"""
245
Generate successful association response.
246
247
Parameters:
248
- assoc: Association object with shared secret
249
250
Returns:
251
OpenIDResponse with association data
252
"""
253
254
def answerUnsupported(self, message, preferred_association_type=None, preferred_session_type=None):
255
"""
256
Generate unsupported association type response.
257
258
Parameters:
259
- message: str, error message
260
- preferred_association_type: str, preferred association type
261
- preferred_session_type: str, preferred session type
262
263
Returns:
264
OpenIDResponse with error and preferences
265
"""
266
```
267
268
### Check Authentication Requests
269
270
Handle signature verification requests from consumers.
271
272
```python { .api }
273
class CheckAuthRequest(OpenIDRequest):
274
"""OpenID check_authentication request.
275
276
A request to verify the validity of a previous response signature.
277
278
Attributes:
279
- mode: "check_authentication"
280
- assoc_handle: str, association handle the response was signed with
281
- signed: Message, the message with signature to verify
282
- invalidate_handle: str, optional association handle to check validity
283
"""
284
285
mode = "check_authentication"
286
required_fields = ["identity", "return_to", "response_nonce"]
287
288
def __init__(self, assoc_handle, signed, invalidate_handle=None):
289
"""
290
Construct CheckAuthRequest.
291
292
Parameters:
293
- assoc_handle: str, association handle for signature verification
294
- signed: Message, signed message to verify
295
- invalidate_handle: str, optional handle to invalidate
296
"""
297
298
@classmethod
299
def fromMessage(cls, message, op_endpoint=None):
300
"""
301
Create CheckAuthRequest from OpenID message.
302
303
Parameters:
304
- message: Message object
305
- op_endpoint: str, server endpoint URL
306
307
Returns:
308
CheckAuthRequest object
309
"""
310
311
def answer(self, signatory):
312
"""
313
Generate signature verification response.
314
315
Parameters:
316
- signatory: Signatory object for verification
317
318
Returns:
319
OpenIDResponse with verification result
320
"""
321
```
322
323
### Response Generation
324
325
Generate and encode OpenID responses with proper formatting and signing.
326
327
```python { .api }
328
class OpenIDResponse:
329
"""Base class for OpenID responses.
330
331
Represents a response to an OpenID request with fields and encoding methods.
332
333
Attributes:
334
- request: OpenIDRequest, the original request being responded to
335
- fields: Message, response fields and parameters
336
"""
337
338
def __init__(self, request):
339
"""
340
Initialize OpenID response.
341
342
Parameters:
343
- request: OpenIDRequest, the request this response answers
344
"""
345
346
def toFormMarkup(self, form_tag_attrs=None):
347
"""
348
Generate HTML form markup for response.
349
350
Parameters:
351
- form_tag_attrs: dict, additional form tag attributes
352
353
Returns:
354
str, HTML form markup
355
"""
356
357
def toHTML(self, form_tag_attrs=None):
358
"""
359
Generate complete HTML page with auto-submitting form.
360
361
Parameters:
362
- form_tag_attrs: dict, additional form tag attributes
363
364
Returns:
365
str, complete HTML page
366
"""
367
368
def renderAsForm(self):
369
"""
370
Check if response should be rendered as HTML form.
371
372
Returns:
373
bool, True if form rendering required
374
"""
375
376
def needsSigning(self):
377
"""
378
Check if response needs to be signed.
379
380
Returns:
381
bool, True if signing required
382
"""
383
384
def whichEncoding(self):
385
"""
386
Determine appropriate encoding method for response.
387
388
Returns:
389
tuple, encoding method identifier
390
"""
391
392
def encodeToURL(self):
393
"""
394
Encode response as URL query parameters.
395
396
Returns:
397
str, URL with encoded response
398
"""
399
400
def addExtension(self, extension_response):
401
"""
402
Add extension data to response.
403
404
Parameters:
405
- extension_response: Extension response object
406
"""
407
408
def encodeToKVForm(self):
409
"""
410
Encode response as key-value form.
411
412
Returns:
413
str, key-value form data
414
"""
415
416
class WebResponse:
417
"""HTTP response wrapper for OpenID server responses.
418
419
Encapsulates HTTP response data including status code, headers, and body
420
for transmission back to the client.
421
422
Attributes:
423
- code: int, HTTP status code (default: 200)
424
- headers: dict, HTTP response headers (default: {})
425
- body: str/bytes, response body content
426
"""
427
428
def __init__(self, code=200, headers=None, body=""):
429
"""
430
Initialize HTTP response.
431
432
Parameters:
433
- code: int, HTTP status code
434
- headers: dict, HTTP headers
435
- body: str, response body
436
"""
437
```
438
439
### Message Signing
440
441
Handle message signing and verification with association management.
442
443
```python { .api }
444
class Signatory:
445
"""Message signing and verification component.
446
447
Handles message signing with associations and signature verification.
448
Manages association lifecycle including creation and invalidation.
449
450
Attributes:
451
- store: OpenIDStore, storage backend for associations and nonces
452
"""
453
454
def __init__(self, store):
455
"""
456
Initialize signatory with storage backend.
457
458
Parameters:
459
- store: OpenIDStore instance
460
"""
461
462
def verify(self, assoc_handle, message):
463
"""
464
Verify message signature using association.
465
466
Parameters:
467
- assoc_handle: str, association handle
468
- message: Message object to verify
469
470
Returns:
471
bool, True if signature valid
472
"""
473
474
def sign(self, response):
475
"""
476
Sign response message.
477
478
Parameters:
479
- response: OpenIDResponse object to sign
480
481
Returns:
482
Association object used for signing
483
"""
484
485
def createAssociation(self, dumb=True, assoc_type='HMAC-SHA1'):
486
"""
487
Create new association.
488
489
Parameters:
490
- dumb: bool, whether association is for dumb mode
491
- assoc_type: str, association type ('HMAC-SHA1' or 'HMAC-SHA256')
492
493
Returns:
494
Association object
495
"""
496
497
def getAssociation(self, assoc_handle, dumb, checkExpiration=True):
498
"""
499
Retrieve association by handle.
500
501
Parameters:
502
- assoc_handle: str, association handle
503
- dumb: bool, whether association is for dumb mode
504
- checkExpiration: bool, whether to check expiration
505
506
Returns:
507
Association object or None
508
"""
509
510
def invalidate(self, assoc_handle, dumb):
511
"""
512
Invalidate association.
513
514
Parameters:
515
- assoc_handle: str, association handle to invalidate
516
- dumb: bool, whether association is for dumb mode
517
"""
518
```
519
520
### Message Encoding and Decoding
521
522
Handle message encoding for responses and decoding of incoming requests.
523
524
```python { .api }
525
class Encoder:
526
"""Base encoder for OpenID responses to WebResponse format.
527
528
Encodes OpenIDResponse objects into WebResponse objects for HTTP transmission.
529
530
Attributes:
531
- responseFactory: class, WebResponse factory for creating responses (default: WebResponse)
532
"""
533
534
responseFactory = 'WebResponse'
535
536
def encode(self, response):
537
"""
538
Encode OpenIDResponse to WebResponse.
539
540
Parameters:
541
- response: OpenIDResponse, response object to encode
542
543
Returns:
544
WebResponse object with appropriate encoding (URL redirect, HTML form, or key-value form)
545
546
Raises:
547
EncodingError when response cannot be encoded as protocol message
548
"""
549
550
class SigningEncoder(Encoder):
551
"""Encoder that signs responses before encoding.
552
553
Extends Encoder to automatically sign responses that require signing
554
before converting to WebResponse format.
555
556
Attributes:
557
- signatory: Signatory, component used for signing responses
558
"""
559
560
def __init__(self, signatory):
561
"""
562
Create SigningEncoder with signatory.
563
564
Parameters:
565
- signatory: Signatory, component for message signing
566
"""
567
568
def encode(self, response):
569
"""
570
Encode OpenIDResponse to WebResponse, signing first if needed.
571
572
Parameters:
573
- response: OpenIDResponse, response object to encode and potentially sign
574
575
Returns:
576
WebResponse object with signed response data
577
578
Raises:
579
EncodingError when response cannot be encoded
580
AlreadySigned when response is already signed
581
"""
582
583
class Decoder:
584
"""Decoder for incoming OpenID requests from query parameters.
585
586
Transforms HTTP query parameters into appropriate OpenIDRequest objects
587
based on the openid.mode parameter.
588
589
Attributes:
590
- server: Server, the server instance this decoder works for
591
- _handlers: dict, mapping of modes to request handler classes
592
"""
593
594
_handlers = {
595
'checkid_setup': 'CheckIDRequest.fromMessage',
596
'checkid_immediate': 'CheckIDRequest.fromMessage',
597
'check_authentication': 'CheckAuthRequest.fromMessage',
598
'associate': 'AssociateRequest.fromMessage',
599
}
600
601
def __init__(self, server):
602
"""
603
Construct Decoder.
604
605
Parameters:
606
- server: Server, the server instance for request processing
607
"""
608
609
def decode(self, query):
610
"""
611
Transform query parameters into OpenIDRequest.
612
613
Parameters:
614
- query: dict, HTTP query parameters with each key mapping to one value
615
616
Returns:
617
OpenIDRequest subclass instance or None if not an OpenID request
618
619
Raises:
620
ProtocolError when query appears to be OpenID request but is invalid
621
"""
622
623
def defaultDecoder(self, message, server):
624
"""
625
Called when no handler found for the request mode.
626
627
Parameters:
628
- message: Message, the decoded message object
629
- server: Server, the server instance
630
631
Returns:
632
None (default behavior for unknown modes)
633
"""
634
```
635
636
### Session Types
637
638
Handle different session types for association establishment.
639
640
```python { .api }
641
class PlainTextServerSession:
642
"""Plain text session type (no encryption).
643
644
Handles association requests where no encryption is used for the shared secret.
645
The secret is transmitted in base64 encoded form.
646
647
Attributes:
648
- session_type: "no-encryption"
649
- allowed_assoc_types: list, supported association types ["HMAC-SHA1", "HMAC-SHA256"]
650
"""
651
652
session_type = 'no-encryption'
653
allowed_assoc_types = ['HMAC-SHA1', 'HMAC-SHA256']
654
655
@classmethod
656
def fromMessage(cls, unused_request):
657
"""
658
Create session from association request.
659
660
Parameters:
661
- unused_request: AssociateRequest object (not used for plain text)
662
663
Returns:
664
PlainTextServerSession object
665
"""
666
667
def answer(self, secret):
668
"""
669
Generate session response with shared secret.
670
671
Parameters:
672
- secret: bytes, shared secret
673
674
Returns:
675
dict, session response data
676
"""
677
678
class DiffieHellmanSHA1ServerSession:
679
"""Diffie-Hellman SHA1 session type.
680
681
Handles association requests using Diffie-Hellman key exchange with SHA1 hashing.
682
Encrypts the shared secret using the consumer's public key.
683
684
Attributes:
685
- session_type: "DH-SHA1"
686
- hash_func: function, SHA1 hash function for key derivation
687
- allowed_assoc_types: list, supported association types ["HMAC-SHA1"]
688
- dh: DiffieHellman, DH algorithm values for this request
689
- consumer_pubkey: long, consumer's public key from the request
690
"""
691
692
session_type = 'DH-SHA1'
693
hash_func = 'sha1'
694
allowed_assoc_types = ['HMAC-SHA1']
695
696
def __init__(self, dh, consumer_pubkey):
697
"""
698
Initialize DH SHA1 session.
699
700
Parameters:
701
- dh: DiffieHellman, DH algorithm parameters
702
- consumer_pubkey: long, consumer's public key
703
"""
704
705
@classmethod
706
def fromMessage(cls, message):
707
"""
708
Create DH session from association request.
709
710
Parameters:
711
- message: Message object with DH parameters
712
713
Returns:
714
DiffieHellmanSHA1ServerSession object
715
"""
716
717
def answer(self, secret):
718
"""
719
Generate DH session response with encrypted secret.
720
721
Parameters:
722
- secret: bytes, shared secret
723
724
Returns:
725
dict, DH session response data
726
"""
727
728
class DiffieHellmanSHA256ServerSession(DiffieHellmanSHA1ServerSession):
729
"""Diffie-Hellman SHA256 session type.
730
731
Extends DiffieHellmanSHA1ServerSession with SHA256 hashing instead of SHA1.
732
Provides stronger cryptographic hashing for the key derivation process.
733
734
Attributes:
735
- session_type: "DH-SHA256"
736
- hash_func: function, SHA256 hash function for key derivation
737
- allowed_assoc_types: list, supported association types ["HMAC-SHA256"]
738
"""
739
740
session_type = 'DH-SHA256'
741
hash_func = 'sha256'
742
allowed_assoc_types = ['HMAC-SHA256']
743
744
@classmethod
745
def fromMessage(cls, message):
746
"""
747
Create DH SHA256 session from association request.
748
749
Parameters:
750
- message: Message object with DH parameters
751
752
Returns:
753
DiffieHellmanSHA256ServerSession object
754
"""
755
756
def answer(self, secret):
757
"""
758
Generate DH SHA256 session response.
759
760
Parameters:
761
- secret: bytes, shared secret
762
763
Returns:
764
dict, DH session response data
765
"""
766
```
767
768
## Usage Examples
769
770
### Basic Server Setup
771
772
```python
773
from openid.server import server
774
from openid.store.filestore import FileOpenIDStore
775
776
# Initialize server
777
store = FileOpenIDStore('/tmp/openid_store')
778
openid_server = server.Server(store, op_endpoint="https://myop.com/openid")
779
780
# Handle incoming request
781
def handle_openid_request(request):
782
query = dict(request.GET.items())
783
openid_request = openid_server.decodeRequest(query)
784
785
if openid_request is None:
786
return HttpResponse("Invalid OpenID request", status=400)
787
788
if isinstance(openid_request, server.CheckIDRequest):
789
return handle_checkid_request(openid_request)
790
elif isinstance(openid_request, server.AssociateRequest):
791
return handle_associate_request(openid_request)
792
elif isinstance(openid_request, server.CheckAuthRequest):
793
return handle_checkauth_request(openid_request)
794
```
795
796
### Handling Authentication Requests
797
798
```python
799
def handle_checkid_request(openid_request):
800
# Validate trust root and return_to
801
if not openid_request.trustRootValid():
802
return HttpResponse("Invalid trust root", status=400)
803
804
if not openid_request.returnToVerified():
805
return HttpResponse("Invalid return_to URL", status=400)
806
807
# Check if user is authenticated
808
if user_is_authenticated(request):
809
# User is authenticated, generate positive response
810
openid_response = openid_request.answer(
811
allow=True,
812
server_url="https://myop.com/openid",
813
identity=get_user_identity_url(),
814
claimed_id=get_user_claimed_id()
815
)
816
817
# Add extension data if requested
818
sreg_request = sreg.SRegRequest.fromOpenIDRequest(openid_request)
819
if sreg_request:
820
sreg_response = sreg.SRegResponse.extractResponse(
821
sreg_request,
822
get_user_profile_data()
823
)
824
openid_response.addExtension(sreg_response)
825
else:
826
# User not authenticated, redirect to login
827
if openid_request.immediate:
828
# Immediate mode - cannot show login page
829
openid_response = openid_request.answer(allow=False)
830
else:
831
# Setup mode - redirect to login page
832
login_url = build_login_url(openid_request)
833
return HttpResponseRedirect(login_url)
834
835
# Encode and return response
836
web_response = openid_server.encodeResponse(openid_response)
837
return HttpResponse(
838
web_response.body,
839
status=web_response.code,
840
content_type='text/html'
841
)
842
```
843
844
### Handling Association Requests
845
846
```python
847
def handle_associate_request(openid_request):
848
openid_response = openid_server.openid_associate(openid_request)
849
web_response = openid_server.encodeResponse(openid_response)
850
return HttpResponse(
851
web_response.body,
852
status=web_response.code,
853
content_type='text/plain'
854
)
855
```
856
857
## Types
858
859
```python { .api }
860
# HTTP Status Codes
861
HTTP_OK = 200
862
HTTP_REDIRECT = 302
863
HTTP_ERROR = 400
864
865
# Request Modes
866
BROWSER_REQUEST_MODES = ['checkid_setup', 'checkid_immediate']
867
868
# Encoding Types
869
ENCODE_KVFORM = ('kvform',)
870
ENCODE_URL = ('URL/redirect',)
871
ENCODE_HTML_FORM = ('HTML form',)
872
873
# Exception Types
874
class ProtocolError(Exception):
875
"""OpenID protocol violation.
876
877
Raised when an OpenID protocol error occurs during request processing.
878
879
Attributes:
880
- message: Message, the OpenID message that caused the error
881
- text: str, human-readable error description
882
- reference: str, optional reference to specification
883
- contact: str, optional contact information for debugging
884
"""
885
886
def __init__(self, message, text=None, reference=None, contact=None):
887
"""
888
Initialize protocol error.
889
890
Parameters:
891
- message: Message, OpenID message causing the error
892
- text: str, error description
893
- reference: str, specification reference
894
- contact: str, contact information
895
"""
896
897
class VersionError(Exception):
898
"""Unsupported OpenID version.
899
900
Raised when an operation is attempted that is not compatible with
901
the protocol version being used.
902
"""
903
904
class NoReturnToError(Exception):
905
"""Missing return_to parameter.
906
907
Raised when a response cannot be generated because the request
908
contains no return_to URL for redirecting the user back.
909
"""
910
911
class EncodingError(Exception):
912
"""Response encoding error.
913
914
Raised when a response cannot be encoded as a protocol message
915
and should probably be rendered and shown to the user.
916
917
Attributes:
918
- response: OpenIDResponse, the response that failed to encode
919
- explanation: str, optional detailed explanation of the error
920
"""
921
922
def __init__(self, response, explanation=None):
923
"""
924
Initialize encoding error.
925
926
Parameters:
927
- response: OpenIDResponse, response that failed to encode
928
- explanation: str, detailed error explanation
929
"""
930
931
class AlreadySigned(EncodingError):
932
"""Response already signed.
933
934
Raised when attempting to sign a response that has already been signed.
935
Inherits from EncodingError.
936
"""
937
938
class UntrustedReturnURL(ProtocolError):
939
"""return_to URL not trusted.
940
941
Raised when a return_to URL is outside the declared trust_root.
942
Inherits from ProtocolError.
943
944
Attributes:
945
- return_to: str, the untrusted return_to URL
946
- trust_root: str, the declared trust root
947
"""
948
949
def __init__(self, message, return_to, trust_root):
950
"""
951
Initialize untrusted return URL error.
952
953
Parameters:
954
- message: Message, the OpenID message
955
- return_to: str, the untrusted return_to URL
956
- trust_root: str, the declared trust root
957
"""
958
959
class MalformedReturnURL(ProtocolError):
960
"""Malformed return_to URL.
961
962
Raised when the return_to URL doesn't look like a valid URL.
963
Inherits from ProtocolError.
964
965
Attributes:
966
- return_to: str, the malformed return_to URL
967
"""
968
969
def __init__(self, openid_message, return_to):
970
"""
971
Initialize malformed return URL error.
972
973
Parameters:
974
- openid_message: Message, the OpenID message
975
- return_to: str, the malformed return_to URL
976
"""
977
978
class MalformedTrustRoot(ProtocolError):
979
"""Malformed trust root.
980
981
Raised when the trust root is not well-formed according to
982
the OpenID specification trust_root format.
983
Inherits from ProtocolError.
984
"""
985
```