Merge pull request #3 from guyrt/master
[twitter-api-cdsw] / oauthlib / oauth1 / rfc5849 / request_validator.py
1 # -*- coding: utf-8 -*-
2 """
3 oauthlib.oauth1.rfc5849
4 ~~~~~~~~~~~~~~
5
6 This module is an implementation of various logic needed
7 for signing and checking OAuth 1.0 RFC 5849 requests.
8 """
9 from __future__ import absolute_import, unicode_literals
10
11 from . import SIGNATURE_METHODS, utils
12
13
14 class RequestValidator(object):
15
16     """A validator/datastore interaction base class for OAuth 1 providers.
17
18     OAuth providers should inherit from RequestValidator and implement the
19     methods and properties outlined below. Further details are provided in the
20     documentation for each method and property.
21
22     Methods used to check the format of input parameters. Common tests include
23     length, character set, membership, range or pattern. These tests are
24     referred to as `whitelisting or blacklisting`_. Whitelisting is better
25     but blacklisting can be usefull to spot malicious activity.
26     The following have methods a default implementation:
27
28     - check_client_key
29     - check_request_token
30     - check_access_token
31     - check_nonce
32     - check_verifier
33     - check_realms
34
35     The methods above default to whitelist input parameters, checking that they
36     are alphanumerical and between a minimum and maximum length. Rather than
37     overloading the methods a few properties can be used to configure these
38     methods.
39
40     * @safe_characters -> (character set)
41     * @client_key_length -> (min, max)
42     * @request_token_length -> (min, max)
43     * @access_token_length -> (min, max)
44     * @nonce_length -> (min, max)
45     * @verifier_length -> (min, max)
46     * @realms -> [list, of, realms]
47
48     Methods used to validate/invalidate input parameters. These checks usually
49     hit either persistent or temporary storage such as databases or the
50     filesystem. See each methods documentation for detailed usage.
51     The following methods must be implemented:
52
53     - validate_client_key
54     - validate_request_token
55     - validate_access_token
56     - validate_timestamp_and_nonce
57     - validate_redirect_uri
58     - validate_requested_realms
59     - validate_realms
60     - validate_verifier
61     - invalidate_request_token
62
63     Methods used to retrieve sensitive information from storage.
64     The following methods must be implemented:
65
66     - get_client_secret
67     - get_request_token_secret
68     - get_access_token_secret
69     - get_rsa_key
70     - get_realms
71     - get_default_realms
72     - get_redirect_uri
73
74     Methods used to save credentials.
75     The following methods must be implemented:
76
77     - save_request_token
78     - save_verifier
79     - save_access_token
80
81     Methods used to verify input parameters. This methods are used during
82     authorizing request token by user (AuthorizationEndpoint), to check if
83     parameters are valid. During token authorization request is not signed,
84     thus 'validation' methods can not be used. The following methods must be
85     implemented:
86
87     - verify_realms
88     - verify_request_token
89
90     To prevent timing attacks it is necessary to not exit early even if the
91     client key or resource owner key is invalid. Instead dummy values should
92     be used during the remaining verification process. It is very important
93     that the dummy client and token are valid input parameters to the methods
94     get_client_secret, get_rsa_key and get_(access/request)_token_secret and
95     that the running time of those methods when given a dummy value remain
96     equivalent to the running time when given a valid client/resource owner.
97     The following properties must be implemented:
98
99     * @dummy_client
100     * @dummy_request_token
101     * @dummy_access_token
102
103     Example implementations have been provided, note that the database used is
104     a simple dictionary and serves only an illustrative purpose. Use whichever
105     database suits your project and how to access it is entirely up to you.
106     The methods are introduced in an order which should make understanding
107     their use more straightforward and as such it could be worth reading what
108     follows in chronological order.
109
110     .. _`whitelisting or blacklisting`: http://www.schneier.com/blog/archives/2011/01/whitelisting_vs.html
111     """
112
113     def __init__(self):
114         pass
115
116     @property
117     def allowed_signature_methods(self):
118         return SIGNATURE_METHODS
119
120     @property
121     def safe_characters(self):
122         return set(utils.UNICODE_ASCII_CHARACTER_SET)
123
124     @property
125     def client_key_length(self):
126         return 20, 30
127
128     @property
129     def request_token_length(self):
130         return 20, 30
131
132     @property
133     def access_token_length(self):
134         return 20, 30
135
136     @property
137     def timestamp_lifetime(self):
138         return 600
139
140     @property
141     def nonce_length(self):
142         return 20, 30
143
144     @property
145     def verifier_length(self):
146         return 20, 30
147
148     @property
149     def realms(self):
150         return []
151
152     @property
153     def enforce_ssl(self):
154         return True
155
156     def check_client_key(self, client_key):
157         """Check that the client key only contains safe characters
158         and is no shorter than lower and no longer than upper.
159         """
160         lower, upper = self.client_key_length
161         return (set(client_key) <= self.safe_characters and
162                 lower <= len(client_key) <= upper)
163
164     def check_request_token(self, request_token):
165         """Checks that the request token contains only safe characters
166         and is no shorter than lower and no longer than upper.
167         """
168         lower, upper = self.request_token_length
169         return (set(request_token) <= self.safe_characters and
170                 lower <= len(request_token) <= upper)
171
172     def check_access_token(self, request_token):
173         """Checks that the token contains only safe characters
174         and is no shorter than lower and no longer than upper.
175         """
176         lower, upper = self.access_token_length
177         return (set(request_token) <= self.safe_characters and
178                 lower <= len(request_token) <= upper)
179
180     def check_nonce(self, nonce):
181         """Checks that the nonce only contains only safe characters
182         and is no shorter than lower and no longer than upper.
183         """
184         lower, upper = self.nonce_length
185         return (set(nonce) <= self.safe_characters and
186                 lower <= len(nonce) <= upper)
187
188     def check_verifier(self, verifier):
189         """Checks that the verifier contains only safe characters
190         and is no shorter than lower and no longer than upper.
191         """
192         lower, upper = self.verifier_length
193         return (set(verifier) <= self.safe_characters and
194                 lower <= len(verifier) <= upper)
195
196     def check_realms(self, realms):
197         """Check that the realm is one of a set allowed realms."""
198         return all((r in self.realms for r in realms))
199
200     @property
201     def dummy_client(self):
202         """Dummy client used when an invalid client key is supplied.
203
204         :returns: The dummy client key string.
205
206         The dummy client should be associated with either a client secret,
207         a rsa key or both depending on which signature methods are supported.
208         Providers should make sure that
209
210         get_client_secret(dummy_client)
211         get_rsa_key(dummy_client)
212
213         return a valid secret or key for the dummy client.
214
215         This method is used by
216
217         * AccessTokenEndpoint
218         * RequestTokenEndpoint
219         * ResourceEndpoint
220         * SignatureOnlyEndpoint
221         """
222         raise NotImplementedError("Subclasses must implement this function.")
223
224     @property
225     def dummy_request_token(self):
226         """Dummy request token used when an invalid token was supplied.
227
228         :returns: The dummy request token string.
229
230         The dummy request token should be associated with a request token
231         secret such that get_request_token_secret(.., dummy_request_token)
232         returns a valid secret.
233
234         This method is used by
235
236         * AccessTokenEndpoint
237         """
238         raise NotImplementedError("Subclasses must implement this function.")
239
240     @property
241     def dummy_access_token(self):
242         """Dummy access token used when an invalid token was supplied.
243
244         :returns: The dummy access token string.
245
246         The dummy access token should be associated with an access token
247         secret such that get_access_token_secret(.., dummy_access_token)
248         returns a valid secret.
249
250         This method is used by
251
252         * ResourceEndpoint
253         """
254         raise NotImplementedError("Subclasses must implement this function.")
255
256     def get_client_secret(self, client_key, request):
257         """Retrieves the client secret associated with the client key.
258
259         :param client_key: The client/consumer key.
260         :param request: An oauthlib.common.Request object.
261         :returns: The client secret as a string.
262
263         This method must allow the use of a dummy client_key value.
264         Fetching the secret using the dummy key must take the same amount of
265         time as fetching a secret for a valid client::
266
267             # Unlikely to be near constant time as it uses two database
268             # lookups for a valid client, and only one for an invalid.
269             from your_datastore import ClientSecret
270             if ClientSecret.has(client_key):
271                 return ClientSecret.get(client_key)
272             else:
273                 return 'dummy'
274
275             # Aim to mimic number of latency inducing operations no matter
276             # whether the client is valid or not.
277             from your_datastore import ClientSecret
278             return ClientSecret.get(client_key, 'dummy')
279
280         Note that the returned key must be in plaintext.
281
282         This method is used by
283
284         * AccessTokenEndpoint
285         * RequestTokenEndpoint
286         * ResourceEndpoint
287         * SignatureOnlyEndpoint
288         """
289         raise NotImplementedError("Subclasses must implement this function.")
290
291     def get_request_token_secret(self, client_key, token, request):
292         """Retrieves the shared secret associated with the request token.
293
294         :param client_key: The client/consumer key.
295         :param token: The request token string.
296         :param request: An oauthlib.common.Request object.
297         :returns: The token secret as a string.
298
299         This method must allow the use of a dummy values and the running time
300         must be roughly equivalent to that of the running time of valid values::
301
302             # Unlikely to be near constant time as it uses two database
303             # lookups for a valid client, and only one for an invalid.
304             from your_datastore import RequestTokenSecret
305             if RequestTokenSecret.has(client_key):
306                 return RequestTokenSecret.get((client_key, request_token))
307             else:
308                 return 'dummy'
309
310             # Aim to mimic number of latency inducing operations no matter
311             # whether the client is valid or not.
312             from your_datastore import RequestTokenSecret
313             return ClientSecret.get((client_key, request_token), 'dummy')
314
315         Note that the returned key must be in plaintext.
316
317         This method is used by
318
319         * AccessTokenEndpoint
320         """
321         raise NotImplementedError("Subclasses must implement this function.")
322
323     def get_access_token_secret(self, client_key, token, request):
324         """Retrieves the shared secret associated with the access token.
325
326         :param client_key: The client/consumer key.
327         :param token: The access token string.
328         :param request: An oauthlib.common.Request object.
329         :returns: The token secret as a string.
330
331         This method must allow the use of a dummy values and the running time
332         must be roughly equivalent to that of the running time of valid values::
333
334             # Unlikely to be near constant time as it uses two database
335             # lookups for a valid client, and only one for an invalid.
336             from your_datastore import AccessTokenSecret
337             if AccessTokenSecret.has(client_key):
338                 return AccessTokenSecret.get((client_key, request_token))
339             else:
340                 return 'dummy'
341
342             # Aim to mimic number of latency inducing operations no matter
343             # whether the client is valid or not.
344             from your_datastore import AccessTokenSecret
345             return ClientSecret.get((client_key, request_token), 'dummy')
346
347         Note that the returned key must be in plaintext.
348
349         This method is used by
350
351         * ResourceEndpoint
352         """
353         raise NotImplementedError("Subclasses must implement this function.")
354
355     def get_default_realms(self, client_key, request):
356         """Get the default realms for a client.
357
358         :param client_key: The client/consumer key.
359         :param request: An oauthlib.common.Request object.
360         :returns: The list of default realms associated with the client.
361
362         The list of default realms will be set during client registration and
363         is outside the scope of OAuthLib.
364
365         This method is used by
366
367         * RequestTokenEndpoint
368         """
369         raise NotImplementedError("Subclasses must implement this function.")
370
371     def get_realms(self, token, request):
372         """Get realms associated with a request token.
373
374         :param token: The request token string.
375         :param request: An oauthlib.common.Request object.
376         :returns: The list of realms associated with the request token.
377
378         This method is used by
379
380         * AuthorizationEndpoint
381         * AccessTokenEndpoint
382         """
383         raise NotImplementedError("Subclasses must implement this function.")
384
385     def get_redirect_uri(self, token, request):
386         """Get the redirect URI associated with a request token.
387
388         :param token: The request token string.
389         :param request: An oauthlib.common.Request object.
390         :returns: The redirect URI associated with the request token.
391
392         It may be desirable to return a custom URI if the redirect is set to "oob".
393         In this case, the user will be redirected to the returned URI and at that
394         endpoint the verifier can be displayed.
395
396         This method is used by
397
398         * AuthorizationEndpoint
399         """
400         raise NotImplementedError("Subclasses must implement this function.")
401
402     def get_rsa_key(self, client_key, request):
403         """Retrieves a previously stored client provided RSA key.
404
405         :param client_key: The client/consumer key.
406         :param request: An oauthlib.common.Request object.
407         :returns: The rsa public key as a string.
408
409         This method must allow the use of a dummy client_key value. Fetching
410         the rsa key using the dummy key must take the same amount of time
411         as fetching a key for a valid client. The dummy key must also be of
412         the same bit length as client keys.
413
414         Note that the key must be returned in plaintext.
415
416         This method is used by
417
418         * AccessTokenEndpoint
419         * RequestTokenEndpoint
420         * ResourceEndpoint
421         * SignatureOnlyEndpoint
422         """
423         raise NotImplementedError("Subclasses must implement this function.")
424
425     def invalidate_request_token(self, client_key, request_token, request):
426         """Invalidates a used request token.
427
428         :param client_key: The client/consumer key.
429         :param request_token: The request token string.
430         :param request: An oauthlib.common.Request object.
431         :returns: The rsa public key as a string.
432
433         Per `Section 2.3`__ of the spec:
434
435         "The server MUST (...) ensure that the temporary
436         credentials have not expired or been used before."
437
438         .. _`Section 2.3`: http://tools.ietf.org/html/rfc5849#section-2.3
439
440         This method should ensure that provided token won't validate anymore.
441         It can be simply removing RequestToken from storage or setting
442         specific flag that makes it invalid (note that such flag should be
443         also validated during request token validation).
444
445         This method is used by
446
447         * AccessTokenEndpoint
448         """
449         raise NotImplementedError("Subclasses must implement this function.")
450
451     def validate_client_key(self, client_key, request):
452         """Validates that supplied client key is a registered and valid client.
453
454         :param client_key: The client/consumer key.
455         :param request: An oauthlib.common.Request object.
456         :returns: True or False
457
458         Note that if the dummy client is supplied it should validate in same
459         or nearly the same amount of time as a valid one.
460
461         Ensure latency inducing tasks are mimiced even for dummy clients.
462         For example, use::
463
464             from your_datastore import Client
465             try:
466                 return Client.exists(client_key, access_token)
467             except DoesNotExist:
468                 return False
469
470         Rather than::
471
472             from your_datastore import Client
473             if access_token == self.dummy_access_token:
474                 return False
475             else:
476                 return Client.exists(client_key, access_token)
477
478         This method is used by
479
480         * AccessTokenEndpoint
481         * RequestTokenEndpoint
482         * ResourceEndpoint
483         * SignatureOnlyEndpoint
484         """
485         raise NotImplementedError("Subclasses must implement this function.")
486
487     def validate_request_token(self, client_key, token, request):
488         """Validates that supplied request token is registered and valid.
489
490         :param client_key: The client/consumer key.
491         :param token: The request token string.
492         :param request: An oauthlib.common.Request object.
493         :returns: True or False
494
495         Note that if the dummy request_token is supplied it should validate in
496         the same nearly the same amount of time as a valid one.
497
498         Ensure latency inducing tasks are mimiced even for dummy clients.
499         For example, use::
500
501             from your_datastore import RequestToken
502             try:
503                 return RequestToken.exists(client_key, access_token)
504             except DoesNotExist:
505                 return False
506
507         Rather than::
508
509             from your_datastore import RequestToken
510             if access_token == self.dummy_access_token:
511                 return False
512             else:
513                 return RequestToken.exists(client_key, access_token)
514
515         This method is used by
516
517         * AccessTokenEndpoint
518         """
519         raise NotImplementedError("Subclasses must implement this function.")
520
521     def validate_access_token(self, client_key, token, request):
522         """Validates that supplied access token is registered and valid.
523
524         :param client_key: The client/consumer key.
525         :param token: The access token string.
526         :param request: An oauthlib.common.Request object.
527         :returns: True or False
528
529         Note that if the dummy access token is supplied it should validate in
530         the same or nearly the same amount of time as a valid one.
531
532         Ensure latency inducing tasks are mimiced even for dummy clients.
533         For example, use::
534
535             from your_datastore import AccessToken
536             try:
537                 return AccessToken.exists(client_key, access_token)
538             except DoesNotExist:
539                 return False
540
541         Rather than::
542
543             from your_datastore import AccessToken
544             if access_token == self.dummy_access_token:
545                 return False
546             else:
547                 return AccessToken.exists(client_key, access_token)
548
549         This method is used by
550
551         * ResourceEndpoint
552         """
553         raise NotImplementedError("Subclasses must implement this function.")
554
555     def validate_timestamp_and_nonce(self, client_key, timestamp, nonce,
556                                      request, request_token=None, access_token=None):
557         """Validates that the nonce has not been used before.
558
559         :param client_key: The client/consumer key.
560         :param timestamp: The ``oauth_timestamp`` parameter.
561         :param nonce: The ``oauth_nonce`` parameter.
562         :param request_token: Request token string, if any.
563         :param access_token: Access token string, if any.
564         :param request: An oauthlib.common.Request object.
565         :returns: True or False
566
567         Per `Section 3.3`_ of the spec.
568
569         "A nonce is a random string, uniquely generated by the client to allow
570         the server to verify that a request has never been made before and
571         helps prevent replay attacks when requests are made over a non-secure
572         channel.  The nonce value MUST be unique across all requests with the
573         same timestamp, client credentials, and token combinations."
574
575         .. _`Section 3.3`: http://tools.ietf.org/html/rfc5849#section-3.3
576
577         One of the first validation checks that will be made is for the validity
578         of the nonce and timestamp, which are associated with a client key and
579         possibly a token. If invalid then immediately fail the request
580         by returning False. If the nonce/timestamp pair has been used before and
581         you may just have detected a replay attack. Therefore it is an essential
582         part of OAuth security that you not allow nonce/timestamp reuse.
583         Note that this validation check is done before checking the validity of
584         the client and token.::
585
586            nonces_and_timestamps_database = [
587               (u'foo', 1234567890, u'rannoMstrInghere', u'bar')
588            ]
589
590            def validate_timestamp_and_nonce(self, client_key, timestamp, nonce,
591               request_token=None, access_token=None):
592
593               return ((client_key, timestamp, nonce, request_token or access_token)
594                        not in self.nonces_and_timestamps_database)
595
596         This method is used by
597
598         * AccessTokenEndpoint
599         * RequestTokenEndpoint
600         * ResourceEndpoint
601         * SignatureOnlyEndpoint
602         """
603         raise NotImplementedError("Subclasses must implement this function.")
604
605     def validate_redirect_uri(self, client_key, redirect_uri, request):
606         """Validates the client supplied redirection URI.
607
608         :param client_key: The client/consumer key.
609         :param redirect_uri: The URI the client which to redirect back to after
610                              authorization is successful.
611         :param request: An oauthlib.common.Request object.
612         :returns: True or False
613
614         It is highly recommended that OAuth providers require their clients
615         to register all redirection URIs prior to using them in requests and
616         register them as absolute URIs. See `CWE-601`_ for more information
617         about open redirection attacks.
618
619         By requiring registration of all redirection URIs it should be
620         straightforward for the provider to verify whether the supplied
621         redirect_uri is valid or not.
622
623         Alternatively per `Section 2.1`_ of the spec:
624
625         "If the client is unable to receive callbacks or a callback URI has
626         been established via other means, the parameter value MUST be set to
627         "oob" (case sensitive), to indicate an out-of-band configuration."
628
629         .. _`CWE-601`: http://cwe.mitre.org/top25/index.html#CWE-601
630         .. _`Section 2.1`: https://tools.ietf.org/html/rfc5849#section-2.1
631
632         This method is used by
633
634         * RequestTokenEndpoint
635         """
636         raise NotImplementedError("Subclasses must implement this function.")
637
638     def validate_requested_realms(self, client_key, realms, request):
639         """Validates that the client may request access to the realm.
640
641         :param client_key: The client/consumer key.
642         :param realms: The list of realms that client is requesting access to.
643         :param request: An oauthlib.common.Request object.
644         :returns: True or False
645
646         This method is invoked when obtaining a request token and should
647         tie a realm to the request token and after user authorization
648         this realm restriction should transfer to the access token.
649
650         This method is used by
651
652         * RequestTokenEndpoint
653         """
654         raise NotImplementedError("Subclasses must implement this function.")
655
656     def validate_realms(self, client_key, token, request, uri=None,
657                         realms=None):
658         """Validates access to the request realm.
659
660         :param client_key: The client/consumer key.
661         :param token: A request token string.
662         :param request: An oauthlib.common.Request object.
663         :param uri: The URI the realms is protecting.
664         :param realms: A list of realms that must have been granted to
665                        the access token.
666         :returns: True or False
667
668         How providers choose to use the realm parameter is outside the OAuth
669         specification but it is commonly used to restrict access to a subset
670         of protected resources such as "photos".
671
672         realms is a convenience parameter which can be used to provide
673         a per view method pre-defined list of allowed realms.
674
675         Can be as simple as::
676
677             from your_datastore import RequestToken
678             request_token = RequestToken.get(token, None)
679
680             if not request_token:
681                 return False
682             return set(request_token.realms).issuperset(set(realms))
683
684         This method is used by
685
686         * ResourceEndpoint
687         """
688         raise NotImplementedError("Subclasses must implement this function.")
689
690     def validate_verifier(self, client_key, token, verifier, request):
691         """Validates a verification code.
692
693         :param client_key: The client/consumer key.
694         :param token: A request token string.
695         :param verifier: The authorization verifier string.
696         :param request: An oauthlib.common.Request object.
697         :returns: True or False
698
699         OAuth providers issue a verification code to clients after the
700         resource owner authorizes access. This code is used by the client to
701         obtain token credentials and the provider must verify that the
702         verifier is valid and associated with the client as well as the
703         resource owner.
704
705         Verifier validation should be done in near constant time
706         (to avoid verifier enumeration). To achieve this we need a
707         constant time string comparison which is provided by OAuthLib
708         in ``oauthlib.common.safe_string_equals``::
709
710             from your_datastore import Verifier
711             correct_verifier = Verifier.get(client_key, request_token)
712             from oauthlib.common import safe_string_equals
713             return safe_string_equals(verifier, correct_verifier)
714
715         This method is used by
716
717         * AccessTokenEndpoint
718         """
719         raise NotImplementedError("Subclasses must implement this function.")
720
721     def verify_request_token(self, token, request):
722         """Verify that the given OAuth1 request token is valid.
723
724         :param token: A request token string.
725         :param request: An oauthlib.common.Request object.
726         :returns: True or False
727
728         This method is used only in AuthorizationEndpoint to check whether the
729         oauth_token given in the authorization URL is valid or not.
730         This request is not signed and thus similar ``validate_request_token``
731         method can not be used.
732
733         This method is used by
734
735         * AuthorizationEndpoint
736         """
737         raise NotImplementedError("Subclasses must implement this function.")
738
739     def verify_realms(self, token, realms, request):
740         """Verify authorized realms to see if they match those given to token.
741
742         :param token: An access token string.
743         :param realms: A list of realms the client attempts to access.
744         :param request: An oauthlib.common.Request object.
745         :returns: True or False
746
747         This prevents the list of authorized realms sent by the client during
748         the authorization step to be altered to include realms outside what
749         was bound with the request token.
750
751         Can be as simple as::
752
753             valid_realms = self.get_realms(token)
754             return all((r in valid_realms for r in realms))
755
756         This method is used by
757
758         * AuthorizationEndpoint
759         """
760         raise NotImplementedError("Subclasses must implement this function.")
761
762     def save_access_token(self, token, request):
763         """Save an OAuth1 access token.
764
765         :param token: A dict with token credentials.
766         :param request: An oauthlib.common.Request object.
767
768         The token dictionary will at minimum include
769
770         * ``oauth_token`` the access token string.
771         * ``oauth_token_secret`` the token specific secret used in signing.
772         * ``oauth_authorized_realms`` a space separated list of realms.
773
774         Client key can be obtained from ``request.client_key``.
775
776         The list of realms (not joined string) can be obtained from
777         ``request.realm``.
778
779         This method is used by
780
781         * AccessTokenEndpoint
782         """
783         raise NotImplementedError("Subclasses must implement this function.")
784
785     def save_request_token(self, token, request):
786         """Save an OAuth1 request token.
787
788         :param token: A dict with token credentials.
789         :param request: An oauthlib.common.Request object.
790
791         The token dictionary will at minimum include
792
793         * ``oauth_token`` the request token string.
794         * ``oauth_token_secret`` the token specific secret used in signing.
795         * ``oauth_callback_confirmed`` the string ``true``.
796
797         Client key can be obtained from ``request.client_key``.
798
799         This method is used by
800
801         * RequestTokenEndpoint
802         """
803         raise NotImplementedError("Subclasses must implement this function.")
804
805     def save_verifier(self, token, verifier, request):
806         """Associate an authorization verifier with a request token.
807
808         :param token: A request token string.
809         :param verifier A dictionary containing the oauth_verifier and
810                         oauth_token
811         :param request: An oauthlib.common.Request object.
812
813         We need to associate verifiers with tokens for validation during the
814         access token request.
815
816         Note that unlike save_x_token token here is the ``oauth_token`` token
817         string from the request token saved previously.
818
819         This method is used by
820
821         * AuthorizationEndpoint
822         """
823         raise NotImplementedError("Subclasses must implement this function.")

Benjamin Mako Hill || Want to submit a patch?