device.py 4.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. """
  2. oauthlib.oauth2.rfc8628
  3. ~~~~~~~~~~~~~~~~~~~~~~~
  4. This module is an implementation of various logic needed
  5. for consuming and providing OAuth 2.0 Device Authorization RFC8628.
  6. """
  7. from oauthlib.common import add_params_to_uri
  8. from oauthlib.oauth2 import BackendApplicationClient, Client
  9. from oauthlib.oauth2.rfc6749.errors import InsecureTransportError
  10. from oauthlib.oauth2.rfc6749.parameters import prepare_token_request
  11. from oauthlib.oauth2.rfc6749.utils import is_secure_transport, list_to_scope
  12. class DeviceClient(Client):
  13. """A public client utilizing the device authorization workflow.
  14. The client can request an access token using a device code and
  15. a public client id associated with the device code as defined
  16. in RFC8628.
  17. The device authorization grant type can be used to obtain both
  18. access tokens and refresh tokens and is intended to be used in
  19. a scenario where the device being authorized does not have a
  20. user interface that is suitable for performing authentication.
  21. """
  22. grant_type = 'urn:ietf:params:oauth:grant-type:device_code'
  23. def __init__(self, client_id, **kwargs):
  24. super().__init__(client_id, **kwargs)
  25. self.client_secret = kwargs.get('client_secret')
  26. def prepare_request_uri(self, uri, scope=None, **kwargs):
  27. if not is_secure_transport(uri):
  28. raise InsecureTransportError()
  29. scope = self.scope if scope is None else scope
  30. params = [(('client_id', self.client_id)), (('grant_type', self.grant_type))]
  31. if self.client_secret is not None:
  32. params.append(('client_secret', self.client_secret))
  33. if scope:
  34. params.append(('scope', list_to_scope(scope)))
  35. for k in kwargs:
  36. if kwargs[k]:
  37. params.append((str(k), kwargs[k]))
  38. return add_params_to_uri(uri, params)
  39. def prepare_request_body(self, device_code, body='', scope=None,
  40. include_client_id=False, **kwargs):
  41. """Add device_code to request body
  42. The client makes a request to the token endpoint by adding the
  43. device_code as a parameter using the
  44. "application/x-www-form-urlencoded" format to the HTTP request
  45. body.
  46. :param body: Existing request body (URL encoded string) to embed parameters
  47. into. This may contain extra parameters. Default ''.
  48. :param scope: The scope of the access request as described by
  49. `Section 3.3`_.
  50. :param include_client_id: `True` to send the `client_id` in the
  51. body of the upstream request. This is required
  52. if the client is not authenticating with the
  53. authorization server as described in
  54. `Section 3.2.1`_. False otherwise (default).
  55. :type include_client_id: Boolean
  56. :param kwargs: Extra credentials to include in the token request.
  57. The prepared body will include all provided device_code as well as
  58. the ``grant_type`` parameter set to
  59. ``urn:ietf:params:oauth:grant-type:device_code``::
  60. >>> from oauthlib.oauth2 import DeviceClient
  61. >>> client = DeviceClient('your_id', 'your_code')
  62. >>> client.prepare_request_body(scope=['hello', 'world'])
  63. 'grant_type=urn:ietf:params:oauth:grant-type:device_code&scope=hello+world'
  64. .. _`Section 3.2.1`: https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1
  65. .. _`Section 3.3`: https://datatracker.ietf.org/doc/html/rfc6749#section-3.3
  66. .. _`Section 3.4`: https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
  67. """
  68. kwargs['client_id'] = self.client_id
  69. kwargs['include_client_id'] = include_client_id
  70. scope = self.scope if scope is None else scope
  71. return prepare_token_request(self.grant_type, body=body, device_code=device_code,
  72. scope=scope, **kwargs)