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:
|
||||
"""Create token for user and return link"""
|
||||
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})
|
||||
link = request.build_absolute_uri(
|
||||
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.providers import ProviderViewSet
|
||||
from passbook.core.api.sources import SourceViewSet
|
||||
from passbook.core.api.tokens import TokenViewSet
|
||||
from passbook.core.api.users import UserViewSet
|
||||
from passbook.crypto.api import CertificateKeyPairViewSet
|
||||
from passbook.flows.api import FlowStageBindingViewSet, FlowViewSet, StageViewSet
|
||||
|
@ -49,9 +50,12 @@ from passbook.stages.user_write.api import UserWriteStageViewSet
|
|||
router = routers.DefaultRouter()
|
||||
|
||||
router.register("root/messages", MessagesViewSet, basename="messages")
|
||||
|
||||
router.register("core/applications", ApplicationViewSet)
|
||||
router.register("core/groups", GroupViewSet)
|
||||
router.register("core/users", UserViewSet)
|
||||
router.register("core/tokens", TokenViewSet)
|
||||
|
||||
router.register("outposts/outposts", OutpostViewSet)
|
||||
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):
|
||||
"""Intents a Token can be created for."""
|
||||
|
||||
# Single user token
|
||||
# Single use token
|
||||
INTENT_VERIFICATION = "verification"
|
||||
|
||||
# Allow access to API
|
||||
INTENT_API = "api"
|
||||
|
||||
INTENT_RECOVERY = "recovery"
|
||||
|
||||
|
||||
class Token(ExpiringModel):
|
||||
"""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)
|
||||
identifier = models.TextField()
|
||||
intent = models.TextField(
|
||||
choices=TokenIntents.choices, default=TokenIntents.INTENT_VERIFICATION
|
||||
)
|
||||
|
@ -318,6 +321,7 @@ class Token(ExpiringModel):
|
|||
|
||||
verbose_name = _("Token")
|
||||
verbose_name_plural = _("Tokens")
|
||||
unique_together = (("identifier", "user"),)
|
||||
|
||||
|
||||
class PropertyMapping(models.Model):
|
||||
|
|
|
@ -148,6 +148,11 @@ class Outpost(models.Model):
|
|||
assign_perm(code_name, user, model)
|
||||
return user
|
||||
|
||||
@property
|
||||
def token_identifier(self) -> str:
|
||||
"""Get Token identifier"""
|
||||
return f"pb-outpost-{self.pk}-api"
|
||||
|
||||
@property
|
||||
def token(self) -> Token:
|
||||
"""Get/create token for auto-generated user"""
|
||||
|
@ -156,6 +161,7 @@ class Outpost(models.Model):
|
|||
return token.first()
|
||||
return Token.objects.create(
|
||||
user=self.user,
|
||||
identifier=self.token_identifier,
|
||||
intent=TokenIntents.INTENT_API,
|
||||
description=f"Autogenerated by passbook for Outpost {self.name}",
|
||||
expiring=False,
|
||||
|
|
|
@ -8,7 +8,7 @@ from django.utils.timezone import now
|
|||
from django.utils.translation import gettext as _
|
||||
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
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
@ -47,6 +47,8 @@ class Command(BaseCommand):
|
|||
token = Token.objects.create(
|
||||
expires=expiry,
|
||||
user=user,
|
||||
identifier="recovery",
|
||||
intent=TokenIntents.INTENT_RECOVERY,
|
||||
description=f"Recovery Token generated by {getuser()} on {_now}",
|
||||
)
|
||||
self.stdout.write(
|
||||
|
|
153
swagger.yaml
153
swagger.yaml
|
@ -343,6 +343,131 @@ paths:
|
|||
required: true
|
||||
type: string
|
||||
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/:
|
||||
get:
|
||||
operationId: core_users_list
|
||||
|
@ -5956,6 +6081,34 @@ definitions:
|
|||
attributes:
|
||||
title: Attributes
|
||||
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:
|
||||
required:
|
||||
- username
|
||||
|
|
Reference in New Issue