Skip to content

Commit d6feb72

Browse files
committed
Add Device model
This model represents the device session for the request and response stage See section 3.1(https://datatracker.ietf.org/doc/html/rfc8628#section-3.1) and 3.2(https://datatracker.ietf.org/doc/html/rfc8628#section-3.2)
1 parent c61d852 commit d6feb72

File tree

1 file changed

+87
-1
lines changed

1 file changed

+87
-1
lines changed

oauth2_provider/models.py

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import time
44
import uuid
55
from contextlib import suppress
6-
from datetime import timedelta
6+
from dataclasses import dataclass
7+
from datetime import datetime, timedelta
8+
from datetime import timezone as dt_timezone
79
from urllib.parse import parse_qsl, urlparse
810

911
from django.apps import apps
@@ -86,12 +88,14 @@ class AbstractApplication(models.Model):
8688
)
8789

8890
GRANT_AUTHORIZATION_CODE = "authorization-code"
91+
GRANT_DEVICE_CODE = "urn:ietf:params:oauth:grant-type:device_code"
8992
GRANT_IMPLICIT = "implicit"
9093
GRANT_PASSWORD = "password"
9194
GRANT_CLIENT_CREDENTIALS = "client-credentials"
9295
GRANT_OPENID_HYBRID = "openid-hybrid"
9396
GRANT_TYPES = (
9497
(GRANT_AUTHORIZATION_CODE, _("Authorization code")),
98+
(GRANT_DEVICE_CODE, _("Device Code")),
9599
(GRANT_IMPLICIT, _("Implicit")),
96100
(GRANT_PASSWORD, _("Resource owner password-based")),
97101
(GRANT_CLIENT_CREDENTIALS, _("Client credentials")),
@@ -650,11 +654,93 @@ class Meta(AbstractIDToken.Meta):
650654
swappable = "OAUTH2_PROVIDER_ID_TOKEN_MODEL"
651655

652656

657+
class AbstractDevice(models.Model):
658+
class Meta:
659+
abstract = True
660+
constraints = [
661+
models.UniqueConstraint(
662+
fields=["device_code"],
663+
name="unique_device_code",
664+
),
665+
]
666+
667+
AUTHORIZED = "authorized"
668+
AUTHORIZATION_PENDING = "authorization-pending"
669+
EXPIRED = "expired"
670+
DENIED = "denied"
671+
672+
DEVICE_FLOW_STATUS = (
673+
(AUTHORIZED, _("Authorized")),
674+
(AUTHORIZATION_PENDING, _("Authorization pending")),
675+
(EXPIRED, _("Expired")),
676+
(DENIED, _("Denied")),
677+
)
678+
679+
id = models.BigAutoField(primary_key=True)
680+
device_code = models.CharField(max_length=100, unique=True)
681+
user_code = models.CharField(max_length=100)
682+
scope = models.CharField(max_length=64, default="openid")
683+
interval = models.IntegerField(default=5)
684+
expires = models.DateTimeField()
685+
status = models.CharField(
686+
max_length=64, blank=True, choices=DEVICE_FLOW_STATUS, default=AUTHORIZATION_PENDING
687+
)
688+
client_id = models.CharField(max_length=100, default=generate_client_id, db_index=True)
689+
last_checked = models.DateTimeField(auto_now=True)
690+
691+
692+
class DeviceManager(models.Manager):
693+
def get_by_natural_key(self, client_id, device_code, user_code):
694+
return self.get(client_id=client_id, device_code=device_code, user_code=user_code)
695+
696+
697+
class Device(AbstractDevice):
698+
objects = DeviceManager()
699+
700+
class Meta(AbstractDevice.Meta):
701+
swappable = "OAUTH2_PROVIDER_DEVICE_MODEL"
702+
703+
def natural_key(self):
704+
return (self.client_id, self.device_code, self.user_code)
705+
706+
707+
@dataclass
708+
class DeviceRequest:
709+
client_id: str
710+
scope: str = "openid"
711+
712+
713+
@dataclass
714+
class DeviceCodeResponse:
715+
verification_uri: str
716+
expires_in: int
717+
user_code: int
718+
device_code: str
719+
interval: int
720+
721+
722+
def create_device(device_request: DeviceRequest, device_response: DeviceCodeResponse) -> Device:
723+
now = datetime.now(tz=dt_timezone.utc)
724+
725+
return Device.objects.create(
726+
client_id=device_request.client_id,
727+
device_code=device_response.device_code,
728+
user_code=device_response.user_code,
729+
scope=device_request.scope,
730+
expires=now + timedelta(seconds=device_response.expires_in),
731+
)
732+
733+
653734
def get_application_model():
654735
"""Return the Application model that is active in this project."""
655736
return apps.get_model(oauth2_settings.APPLICATION_MODEL)
656737

657738

739+
def get_device_model():
740+
"""Return the Device model that is active in this project."""
741+
return apps.get_model(oauth2_settings.DEVICE_MODEL)
742+
743+
658744
def get_grant_model():
659745
"""Return the Grant model that is active in this project."""
660746
return apps.get_model(oauth2_settings.GRANT_MODEL)

0 commit comments

Comments
 (0)