Skip to content

Allow user_code to be configured for device auth flow (Device Authorization Grant) #885

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions docs/oauth2/endpoints/device.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
=============
Device
=============

The device endpoint is used to initiate the authorization flow by requesting a set of
verification codes from the authorization server by making an HTTP "POST" request to
the device authorization endpoint.

** Device Authorization Request **
The client makes a device authorization request to the device
authorization endpoint by including the following parameters using
the "application/x-www-form-urlencoded" format:

POST /device_authorization HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
client_id=123456&scope=example_scope

.. code-block:: python

# Initial setup
from your_validator import your_validator
verification_uri = "https://example.com/device"

def user_code():
# some logic to generate a random string...
return "123-456"

# user code is optional
server = DeviceApplicationServer(your_validator, verification_uri, user_code)

headers, data, status = server.create_device_authorization_response(request)

# response from /device_authorization endpoint on your server
from your_framework import http_response
http_response(data, status=status, headers=headers)



.. code-block:: python

# example response
{
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
"user_code": "123-456",
"verification_uri": "https://example.com/device",
"verification_uri_complete":
"https://example.com/device?user_code=WDJB-MJHT",
"expires_in": 1800,
"interval": 5
}


.. autoclass:: oauthlib.oauth2.DeviceAuthorizationEndpoint
:members:
1 change: 1 addition & 0 deletions docs/oauth2/endpoints/endpoints.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ client attempts to access the user resources on their behalf.
:maxdepth: 2

authorization
device
introspect
token
metadata
Expand Down
4 changes: 4 additions & 0 deletions docs/oauth2/preconfigured_servers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ This function is passed the request object and a boolean indicating whether to g

.. autoclass:: oauthlib.oauth2.BackendApplicationServer
:members:


.. autoclass:: oauthlib.oauth2.DeviceApplicationServer
:members:
66 changes: 49 additions & 17 deletions oauthlib/oauth2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,65 @@
This module is a wrapper for the most recent implementation of OAuth 2.0 Client
and Server classes.
"""

from .rfc6749.clients import (
BackendApplicationClient, Client, LegacyApplicationClient,
MobileApplicationClient, ServiceApplicationClient, WebApplicationClient,
BackendApplicationClient,
Client,
LegacyApplicationClient,
MobileApplicationClient,
ServiceApplicationClient,
WebApplicationClient,
)
from .rfc6749.endpoints import (
AuthorizationEndpoint, BackendApplicationServer, IntrospectEndpoint,
LegacyApplicationServer, MetadataEndpoint, MobileApplicationServer,
ResourceEndpoint, RevocationEndpoint, Server, TokenEndpoint,
AuthorizationEndpoint,
BackendApplicationServer,
IntrospectEndpoint,
LegacyApplicationServer,
MetadataEndpoint,
MobileApplicationServer,
ResourceEndpoint,
RevocationEndpoint,
Server,
TokenEndpoint,
WebApplicationServer,
)
from .rfc6749.errors import (
AccessDeniedError, FatalClientError, InsecureTransportError,
InvalidClientError, InvalidClientIdError, InvalidGrantError,
InvalidRedirectURIError, InvalidRequestError, InvalidRequestFatalError,
InvalidScopeError, MismatchingRedirectURIError, MismatchingStateError,
MissingClientIdError, MissingCodeError, MissingRedirectURIError,
MissingResponseTypeError, MissingTokenError, MissingTokenTypeError,
OAuth2Error, ServerError, TemporarilyUnavailableError, TokenExpiredError,
UnauthorizedClientError, UnsupportedGrantTypeError,
UnsupportedResponseTypeError, UnsupportedTokenTypeError,
AccessDeniedError,
FatalClientError,
InsecureTransportError,
InvalidClientError,
InvalidClientIdError,
InvalidGrantError,
InvalidRedirectURIError,
InvalidRequestError,
InvalidRequestFatalError,
InvalidScopeError,
MismatchingRedirectURIError,
MismatchingStateError,
MissingClientIdError,
MissingCodeError,
MissingRedirectURIError,
MissingResponseTypeError,
MissingTokenError,
MissingTokenTypeError,
OAuth2Error,
ServerError,
TemporarilyUnavailableError,
TokenExpiredError,
UnauthorizedClientError,
UnsupportedGrantTypeError,
UnsupportedResponseTypeError,
UnsupportedTokenTypeError,
)
from .rfc6749.grant_types import (
AuthorizationCodeGrant, ClientCredentialsGrant, ImplicitGrant,
RefreshTokenGrant, ResourceOwnerPasswordCredentialsGrant,
AuthorizationCodeGrant,
ClientCredentialsGrant,
ImplicitGrant,
RefreshTokenGrant,
ResourceOwnerPasswordCredentialsGrant,
)
from .rfc6749.request_validator import RequestValidator
from .rfc6749.tokens import BearerToken, OAuth2Token
from .rfc6749.utils import is_secure_transport
from .rfc8628.clients import DeviceClient
from .rfc8628.endpoints import DeviceAuthorizationEndpoint
from .rfc8628.endpoints import DeviceAuthorizationEndpoint, DeviceApplicationServer
1 change: 1 addition & 0 deletions oauthlib/oauth2/rfc8628/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 Device Authorization RFC8628.
"""

import logging

log = logging.getLogger(__name__)
2 changes: 2 additions & 0 deletions oauthlib/oauth2/rfc8628/endpoints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@
This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 Device Authorization RFC8628.
"""

from .device_authorization import DeviceAuthorizationEndpoint
from .pre_configured import DeviceApplicationServer
138 changes: 73 additions & 65 deletions oauthlib/oauth2/rfc8628/endpoints/device_authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,21 @@
This module is an implementation of various logic needed
for consuming and providing OAuth 2.0 RFC8628.
"""
import json

import logging
from typing import Callable

from oauthlib.common import Request, generate_token
from oauthlib.oauth2.rfc6749 import errors
from oauthlib.oauth2.rfc6749.endpoints.base import (
BaseEndpoint, catch_errors_and_unavailability,
BaseEndpoint,
catch_errors_and_unavailability,
)

log = logging.getLogger(__name__)


class DeviceAuthorizationEndpoint(BaseEndpoint):

"""DeviceAuthorization endpoint - used by the client to initiate
the authorization flow by requesting a set of verification codes
from the authorization server by making an HTTP "POST" request to
Expand All @@ -39,6 +40,7 @@ def __init__(
expires_in=1800,
interval=None,
verification_uri_complete=None,
user_code_generator: Callable[[None], str] = None,
):
"""
:param request_validator: An instance of RequestValidator.
Expand All @@ -47,13 +49,14 @@ def __init__(
:param expires_in: a number that represents the lifetime of the `user_code` and `device_code`
:param interval: an option number that represents the number of seconds between each poll requests
:param verification_uri_complete: a string of a function that can be called with `user_data` as parameter
:param user_code_generator: a callable that returns a configurable user code
"""
self.request_validator = request_validator
self._expires_in = expires_in
self._interval = interval
self._verification_uri = verification_uri
self._verification_uri_complete = verification_uri_complete
self._interval = interval
self.user_code_generator = user_code_generator

BaseEndpoint.__init__(self)

Expand Down Expand Up @@ -141,71 +144,77 @@ def validate_device_authorization_request(self, request):
def create_device_authorization_response(
self, uri, http_method="POST", body=None, headers=None
):
"""create_device_authorization_response - generates a unique device
verification code and an end-user code that are valid for a limited
time and includes them in the HTTP response body using the
"application/json" format [RFC8259] with a 200 (OK) status code, as
described in `Section-3.2`_.

:param uri: The full URI of the token request.
:param request: OAuthlib request.
:type request: oauthlib.common.Request
:returns: A tuple of 3 elements.
1. A dict of headers to set on the response.
2. The response body as a string.
3. The response status code as an integer.

The response contains the following parameters:

device_code
REQUIRED. The device verification code.

user_code
REQUIRED. The end-user verification code.

verification_uri
REQUIRED. The end-user verification URI on the authorization
server. The URI should be short and easy to remember as end users
will be asked to manually type it into their user agent.

verification_uri_complete
OPTIONAL. A verification URI that includes the "user_code" (or
other information with the same function as the "user_code"),
which is designed for non-textual transmission.

expires_in
REQUIRED. The lifetime in seconds of the "device_code" and
"user_code".

interval
OPTIONAL. The minimum amount of time in seconds that the client
SHOULD wait between polling requests to the token endpoint. If no
value is provided, clients MUST use 5 as the default.

For example:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
"user_code": "WDJB-MJHT",
"verification_uri": "https://example.com/device",
"verification_uri_complete":
"https://example.com/device?user_code=WDJB-MJHT",
"expires_in": 1800,
"interval": 5
}

.. _`Section-3.2`: https://www.rfc-editor.org/rfc/rfc8628#section-3.2
"""
Generate a unique device verification code and an end-user code that are valid for a limited time.
Include them in the HTTP response body using the "application/json" format [RFC8259] with a
200 (OK) status code, as described in `Section-3.2`_.

:param uri: The full URI of the token request.
:type uri: str
:param request: OAuthlib request.
:type request: oauthlib.common.Request
:param user_code_generator:
A callable that returns a string for the user code.
This allows the caller to decide how the `user_code` should be formatted.
:type user_code_generator: Callable[[], str]
:return: A tuple of three elements:
1. A dict of headers to set on the response.
2. The response body as a string.
3. The response status code as an integer.
:rtype: tuple

The response contains the following parameters:

device_code
**REQUIRED.** The device verification code.

user_code
**REQUIRED.** The end-user verification code.

verification_uri
**REQUIRED.** The end-user verification URI on the authorization server.
The URI should be short and easy to remember as end users will be asked
to manually type it into their user agent.

verification_uri_complete
**OPTIONAL.** A verification URI that includes the `user_code` (or
other information with the same function as the `user_code`), which is
designed for non-textual transmission.

expires_in
**REQUIRED.** The lifetime in seconds of the `device_code` and `user_code`.

interval
**OPTIONAL.** The minimum amount of time in seconds that the client
SHOULD wait between polling requests to the token endpoint. If no
value is provided, clients MUST use 5 as the default.

**For example:**

.. code-block:: http

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
"user_code": "WDJB-MJHT",
"verification_uri": "https://example.com/device",
"verification_uri_complete":
"https://example.com/device?user_code=WDJB-MJHT",
"expires_in": 1800,
"interval": 5
}

.. _`Section-3.2`: https://www.rfc-editor.org/rfc/rfc8628#section-3.2
"""
request = Request(uri, http_method, body, headers)
self.validate_device_authorization_request(request)
log.debug("Pre resource owner authorization validation ok for %r.", request)

headers = {}
user_code = generate_token()
user_code = self.user_code_generator() if self.user_code_generator else generate_token()
data = {
"verification_uri": self.verification_uri,
"expires_in": self.expires_in,
Expand All @@ -219,5 +228,4 @@ def create_device_authorization_response(
if verification_uri_complete:
data["verification_uri_complete"] = verification_uri_complete

body = json.dumps(data)
return headers, body, 200
return headers, data, 200
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
from oauthlib.oauth2.rfc8628.endpoints.device_authorization import (
DeviceAuthorizationEndpoint,
)
from typing import Callable


class DeviceApplicationServer(DeviceAuthorizationEndpoint):

"""An all-in-one endpoint featuring Authorization code grant and Bearer tokens."""

def __init__(self, request_validator, verification_uri, **kwargs):
def __init__(
self,
request_validator,
verification_uri,
user_code_generator: Callable[[None], str] = None,
**kwargs,
):
"""Construct a new web application server.

:param request_validator: An implementation of
oauthlib.oauth2.rfc8626.RequestValidator.
:param verification_uri: the verification_uri to be send back.
:param user_code_generator: a callable that allows the user code to be configured.
"""
DeviceAuthorizationEndpoint.__init__(
self, request_validator, verification_uri=verification_uri
self,
request_validator,
verification_uri=verification_uri,
user_code_generator=user_code_generator,
)
Loading
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy