core: Add Token identifier as sudo-primary key
This commit is contained in:
parent
b590589324
commit
c5a6b4961f
|
@ -107,7 +107,9 @@ class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailV
|
||||||
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
||||||
"""Create token for user and return link"""
|
"""Create token for user and return link"""
|
||||||
super().get(request, *args, **kwargs)
|
super().get(request, *args, **kwargs)
|
||||||
token = Token.objects.create(user=self.object)
|
token, _ = Token.objects.get_or_create(
|
||||||
|
identifier="password-reset-temp", user=self.object
|
||||||
|
)
|
||||||
querystring = urlencode({"token": token.token_uuid})
|
querystring = urlencode({"token": token.token_uuid})
|
||||||
link = request.build_absolute_uri(
|
link = request.build_absolute_uri(
|
||||||
reverse("passbook_flows:default-recovery") + f"?{querystring}"
|
reverse("passbook_flows:default-recovery") + f"?{querystring}"
|
||||||
|
|
|
@ -12,6 +12,7 @@ from passbook.core.api.groups import GroupViewSet
|
||||||
from passbook.core.api.propertymappings import PropertyMappingViewSet
|
from passbook.core.api.propertymappings import PropertyMappingViewSet
|
||||||
from passbook.core.api.providers import ProviderViewSet
|
from passbook.core.api.providers import ProviderViewSet
|
||||||
from passbook.core.api.sources import SourceViewSet
|
from passbook.core.api.sources import SourceViewSet
|
||||||
|
from passbook.core.api.tokens import TokenViewSet
|
||||||
from passbook.core.api.users import UserViewSet
|
from passbook.core.api.users import UserViewSet
|
||||||
from passbook.crypto.api import CertificateKeyPairViewSet
|
from passbook.crypto.api import CertificateKeyPairViewSet
|
||||||
from passbook.flows.api import FlowStageBindingViewSet, FlowViewSet, StageViewSet
|
from passbook.flows.api import FlowStageBindingViewSet, FlowViewSet, StageViewSet
|
||||||
|
@ -49,9 +50,12 @@ from passbook.stages.user_write.api import UserWriteStageViewSet
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
|
|
||||||
router.register("root/messages", MessagesViewSet, basename="messages")
|
router.register("root/messages", MessagesViewSet, basename="messages")
|
||||||
|
|
||||||
router.register("core/applications", ApplicationViewSet)
|
router.register("core/applications", ApplicationViewSet)
|
||||||
router.register("core/groups", GroupViewSet)
|
router.register("core/groups", GroupViewSet)
|
||||||
router.register("core/users", UserViewSet)
|
router.register("core/users", UserViewSet)
|
||||||
|
router.register("core/tokens", TokenViewSet)
|
||||||
|
|
||||||
router.register("outposts/outposts", OutpostViewSet)
|
router.register("outposts/outposts", OutpostViewSet)
|
||||||
router.register("outposts/proxy", OutpostConfigViewSet)
|
router.register("outposts/proxy", OutpostConfigViewSet)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
"""Tokens API Viewset"""
|
||||||
|
from rest_framework.serializers import ModelSerializer
|
||||||
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
from passbook.core.models import Token
|
||||||
|
|
||||||
|
|
||||||
|
class TokenSerializer(ModelSerializer):
|
||||||
|
"""Token Serializer"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
|
||||||
|
model = Token
|
||||||
|
fields = ["pk", "identifier", "intent", "user", "description"]
|
||||||
|
|
||||||
|
|
||||||
|
class TokenViewSet(ModelViewSet):
|
||||||
|
"""Token Viewset"""
|
||||||
|
|
||||||
|
queryset = Token.objects.all()
|
||||||
|
lookup_field = "identifier"
|
||||||
|
serializer_class = TokenSerializer
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Generated by Django 3.1.2 on 2020-10-03 21:32
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("passbook_core", "0012_auto_20201003_1737"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="token",
|
||||||
|
name="identifier",
|
||||||
|
field=models.TextField(default=""),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="token",
|
||||||
|
name="intent",
|
||||||
|
field=models.TextField(
|
||||||
|
choices=[
|
||||||
|
("verification", "Intent Verification"),
|
||||||
|
("api", "Intent Api"),
|
||||||
|
("recovery", "Intent Recovery"),
|
||||||
|
],
|
||||||
|
default="verification",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name="token",
|
||||||
|
unique_together={("identifier", "user")},
|
||||||
|
),
|
||||||
|
]
|
|
@ -292,17 +292,20 @@ class ExpiringModel(models.Model):
|
||||||
class TokenIntents(models.TextChoices):
|
class TokenIntents(models.TextChoices):
|
||||||
"""Intents a Token can be created for."""
|
"""Intents a Token can be created for."""
|
||||||
|
|
||||||
# Single user token
|
# Single use token
|
||||||
INTENT_VERIFICATION = "verification"
|
INTENT_VERIFICATION = "verification"
|
||||||
|
|
||||||
# Allow access to API
|
# Allow access to API
|
||||||
INTENT_API = "api"
|
INTENT_API = "api"
|
||||||
|
|
||||||
|
INTENT_RECOVERY = "recovery"
|
||||||
|
|
||||||
|
|
||||||
class Token(ExpiringModel):
|
class Token(ExpiringModel):
|
||||||
"""Token used to authenticate the User for API Access or confirm another Stage like Email."""
|
"""Token used to authenticate the User for API Access or confirm another Stage like Email."""
|
||||||
|
|
||||||
token_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
token_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
||||||
|
identifier = models.TextField()
|
||||||
intent = models.TextField(
|
intent = models.TextField(
|
||||||
choices=TokenIntents.choices, default=TokenIntents.INTENT_VERIFICATION
|
choices=TokenIntents.choices, default=TokenIntents.INTENT_VERIFICATION
|
||||||
)
|
)
|
||||||
|
@ -318,6 +321,7 @@ class Token(ExpiringModel):
|
||||||
|
|
||||||
verbose_name = _("Token")
|
verbose_name = _("Token")
|
||||||
verbose_name_plural = _("Tokens")
|
verbose_name_plural = _("Tokens")
|
||||||
|
unique_together = (("identifier", "user"),)
|
||||||
|
|
||||||
|
|
||||||
class PropertyMapping(models.Model):
|
class PropertyMapping(models.Model):
|
||||||
|
|
|
@ -148,6 +148,11 @@ class Outpost(models.Model):
|
||||||
assign_perm(code_name, user, model)
|
assign_perm(code_name, user, model)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
@property
|
||||||
|
def token_identifier(self) -> str:
|
||||||
|
"""Get Token identifier"""
|
||||||
|
return f"pb-outpost-{self.pk}-api"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def token(self) -> Token:
|
def token(self) -> Token:
|
||||||
"""Get/create token for auto-generated user"""
|
"""Get/create token for auto-generated user"""
|
||||||
|
@ -156,6 +161,7 @@ class Outpost(models.Model):
|
||||||
return token.first()
|
return token.first()
|
||||||
return Token.objects.create(
|
return Token.objects.create(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
|
identifier=self.token_identifier,
|
||||||
intent=TokenIntents.INTENT_API,
|
intent=TokenIntents.INTENT_API,
|
||||||
description=f"Autogenerated by passbook for Outpost {self.name}",
|
description=f"Autogenerated by passbook for Outpost {self.name}",
|
||||||
expiring=False,
|
expiring=False,
|
||||||
|
|
|
@ -8,7 +8,7 @@ from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from structlog import get_logger
|
from structlog import get_logger
|
||||||
|
|
||||||
from passbook.core.models import Token, User
|
from passbook.core.models import Token, TokenIntents, User
|
||||||
from passbook.lib.config import CONFIG
|
from passbook.lib.config import CONFIG
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
@ -47,6 +47,8 @@ class Command(BaseCommand):
|
||||||
token = Token.objects.create(
|
token = Token.objects.create(
|
||||||
expires=expiry,
|
expires=expiry,
|
||||||
user=user,
|
user=user,
|
||||||
|
identifier="recovery",
|
||||||
|
intent=TokenIntents.INTENT_RECOVERY,
|
||||||
description=f"Recovery Token generated by {getuser()} on {_now}",
|
description=f"Recovery Token generated by {getuser()} on {_now}",
|
||||||
)
|
)
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
|
|
153
swagger.yaml
153
swagger.yaml
|
@ -343,6 +343,131 @@ paths:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
|
/core/tokens/:
|
||||||
|
get:
|
||||||
|
operationId: core_tokens_list
|
||||||
|
description: Token Viewset
|
||||||
|
parameters:
|
||||||
|
- name: ordering
|
||||||
|
in: query
|
||||||
|
description: Which field to use when ordering the results.
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: search
|
||||||
|
in: query
|
||||||
|
description: A search term.
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
description: Number of results to return per page.
|
||||||
|
required: false
|
||||||
|
type: integer
|
||||||
|
- name: offset
|
||||||
|
in: query
|
||||||
|
description: The initial index from which to return the results.
|
||||||
|
required: false
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
required:
|
||||||
|
- count
|
||||||
|
- results
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
count:
|
||||||
|
type: integer
|
||||||
|
next:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
x-nullable: true
|
||||||
|
previous:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
x-nullable: true
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Token'
|
||||||
|
tags:
|
||||||
|
- core
|
||||||
|
post:
|
||||||
|
operationId: core_tokens_create
|
||||||
|
description: Token Viewset
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Token'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Token'
|
||||||
|
tags:
|
||||||
|
- core
|
||||||
|
parameters: []
|
||||||
|
/core/tokens/{identifier}/:
|
||||||
|
get:
|
||||||
|
operationId: core_tokens_read
|
||||||
|
description: Token Viewset
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Token'
|
||||||
|
tags:
|
||||||
|
- core
|
||||||
|
put:
|
||||||
|
operationId: core_tokens_update
|
||||||
|
description: Token Viewset
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Token'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Token'
|
||||||
|
tags:
|
||||||
|
- core
|
||||||
|
patch:
|
||||||
|
operationId: core_tokens_partial_update
|
||||||
|
description: Token Viewset
|
||||||
|
parameters:
|
||||||
|
- name: data
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Token'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: ''
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Token'
|
||||||
|
tags:
|
||||||
|
- core
|
||||||
|
delete:
|
||||||
|
operationId: core_tokens_delete
|
||||||
|
description: Token Viewset
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: ''
|
||||||
|
tags:
|
||||||
|
- core
|
||||||
|
parameters:
|
||||||
|
- name: identifier
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
/core/users/:
|
/core/users/:
|
||||||
get:
|
get:
|
||||||
operationId: core_users_list
|
operationId: core_users_list
|
||||||
|
@ -5956,6 +6081,34 @@ definitions:
|
||||||
attributes:
|
attributes:
|
||||||
title: Attributes
|
title: Attributes
|
||||||
type: string
|
type: string
|
||||||
|
Token:
|
||||||
|
required:
|
||||||
|
- identifier
|
||||||
|
- user
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pk:
|
||||||
|
title: Token uuid
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
readOnly: true
|
||||||
|
identifier:
|
||||||
|
title: Identifier
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
intent:
|
||||||
|
title: Intent
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- verification
|
||||||
|
- api
|
||||||
|
- recovery
|
||||||
|
user:
|
||||||
|
title: User
|
||||||
|
type: integer
|
||||||
|
description:
|
||||||
|
title: Description
|
||||||
|
type: string
|
||||||
User:
|
User:
|
||||||
required:
|
required:
|
||||||
- username
|
- username
|
||||||
|
|
Reference in New Issue