feat: 为rdp 添加一个api

pull/5660/head
ibuler 2021-02-23 14:37:42 +08:00 committed by 老广
parent 9be3cbb936
commit bb9790a50f
12 changed files with 290 additions and 91 deletions

View File

@ -3,6 +3,7 @@ from django.utils.translation import ugettext_lazy as _
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
from common.mixins import CommonModelMixin from common.mixins import CommonModelMixin
from assets.models import Asset
from .. import const from .. import const
@ -35,3 +36,35 @@ class Application(CommonModelMixin, OrgModelMixin):
@property @property
def category_remote_app(self): def category_remote_app(self):
return self.category == const.ApplicationCategoryChoices.remote_app.value return self.category == const.ApplicationCategoryChoices.remote_app.value
def get_rdp_remote_app_setting(self):
from applications.serializers.attrs import get_serializer_class_by_application_type
if not self.category_remote_app:
raise ValueError(f"Not a remote app application: {self.name}")
serializer_class = get_serializer_class_by_application_type(self.type)
fields = serializer_class().get_fields()
parameters = [self.type]
for field_name in list(fields.keys()):
if field_name in ['asset']:
continue
value = self.attrs.get(field_name)
if not value:
continue
if field_name == 'path':
value = '\"%s\"' % value
parameters.append(str(value))
parameters = ' '.join(parameters)
return {
'program': '||jmservisor',
'working_directory': '',
'parameters': parameters
}
def get_remote_app_asset(self):
asset_id = self.attrs.get('asset')
if not asset_id:
raise ValueError("Remote App not has asset attr")
asset = Asset.objects.filter(id=asset_id).first()
return asset

View File

@ -27,31 +27,5 @@ class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer):
return obj.attrs.get('asset') return obj.attrs.get('asset')
@staticmethod @staticmethod
def get_parameters(obj): def get_parameter_remote_app(obj):
""" return obj.get_rdp_remote_app_setting()
返回Guacamole需要的RemoteApp配置参数信息中的parameters参数
"""
from .attrs import get_serializer_class_by_application_type
serializer_class = get_serializer_class_by_application_type(obj.type)
fields = serializer_class().get_fields()
parameters = [obj.type]
for field_name in list(fields.keys()):
if field_name in ['asset']:
continue
value = obj.attrs.get(field_name)
if not value:
continue
if field_name == 'path':
value = '\"%s\"' % value
parameters.append(str(value))
parameters = ' '.join(parameters)
return parameters
def get_parameter_remote_app(self, obj):
return {
'program': '||jmservisor',
'working_directory': '',
'parameters': self.get_parameters(obj)
}

View File

@ -77,8 +77,6 @@ class GatewayWithAuthSerializer(GatewaySerializer):
return fields return fields
class DomainWithGatewaySerializer(BulkOrgResourceModelSerializer): class DomainWithGatewaySerializer(BulkOrgResourceModelSerializer):
gateways = GatewayWithAuthSerializer(many=True, read_only=True) gateways = GatewayWithAuthSerializer(many=True, read_only=True)

View File

@ -1,40 +1,73 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import uuid
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.shortcuts import get_object_or_404
from rest_framework.serializers import ValidationError
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.generics import CreateAPIView from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from common.utils import get_logger, random_string
from common.drf.api import SerializerMixin2
from common.permissions import IsSuperUserOrAppUser, IsValidUser, IsSuperUser
from common.utils import get_logger
from common.permissions import IsSuperUserOrAppUser
from orgs.mixins.api import RootOrgViewMixin from orgs.mixins.api import RootOrgViewMixin
from ..serializers import ConnectionTokenSerializer from ..serializers import ConnectionTokenSerializer, ConnectionTokenSecretSerializer
logger = get_logger(__name__) logger = get_logger(__name__)
__all__ = ['UserConnectionTokenApi'] __all__ = ['UserConnectionTokenViewSet']
class UserConnectionTokenApi(RootOrgViewMixin, CreateAPIView): class UserConnectionTokenViewSet(RootOrgViewMixin, SerializerMixin2, GenericViewSet):
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsSuperUserOrAppUser,)
serializer_class = ConnectionTokenSerializer serializer_classes = {
'default': ConnectionTokenSerializer,
'get_secret_detail': ConnectionTokenSecretSerializer
}
CACHE_KEY_PREFIX = 'CONNECTION_TOKEN_{}'
def perform_create(self, serializer): @staticmethod
user = serializer.validated_data['user'] def check_resource_permission(user, asset, application, system_user):
asset = serializer.validated_data['asset'] from perms.utils.asset import has_asset_system_permission
system_user = serializer.validated_data['system_user'] from perms.utils.application import has_application_system_permission
token = str(uuid.uuid4()) if asset and not has_asset_system_permission(user, asset, system_user):
error = f'User not has this asset and system user permission: ' \
f'user={user.id} system_user={system_user.id} asset={asset.id}'
raise PermissionDenied(error)
if application and not has_application_system_permission(user, application, system_user):
error = f'User not has this application and system user permission: ' \
f'user={user.id} system_user={system_user.id} application={application.id}'
raise PermissionDenied(error)
return True
def create_token(self, user, asset, application, system_user):
self.check_resource_permission(user, asset, application, system_user)
token = random_string(36)
value = { value = {
'user': str(user.id), 'user': str(user.id),
'username': user.username, 'username': user.username,
'asset': str(asset.id),
'hostname': asset.hostname,
'system_user': str(system_user.id), 'system_user': str(system_user.id),
'system_user_name': system_user.name 'system_user_name': system_user.name
} }
cache.set(token, value, timeout=20)
if asset:
value.update({
'type': 'asset',
'asset': str(asset.id),
'hostname': asset.hostname,
})
elif application:
value.update({
'type': 'application',
'application': application.id,
'application_name': str(application)
})
key = self.CACHE_KEY_PREFIX.format(token)
cache.set(key, value, timeout=20)
return token return token
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
@ -42,18 +75,107 @@ class UserConnectionTokenApi(RootOrgViewMixin, CreateAPIView):
data = {'error': 'Connection token disabled'} data = {'error': 'Connection token disabled'}
return Response(data, status=400) return Response(data, status=400)
if not request.user.is_superuser:
data = {'error': 'Only super user can create token'}
return Response(data, status=403)
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
token = self.perform_create(serializer)
user = serializer.validated_data.get('user', None)
if not request.user.is_superuser and user:
raise PermissionDenied('Only super user can create user token')
if not request.user.is_superuser:
user = self.request.user
asset = serializer.validated_data.get('asset')
application = serializer.validated_data.get('application')
system_user = serializer.validated_data['system_user']
token = self.create_token(user, asset, application, system_user)
return Response({"token": token}, status=201) return Response({"token": token}, status=201)
@staticmethod
def _get_application_secret_detail(value):
from applications.models import Application
from perms.models import Action
application = get_object_or_404(Application, id=value.get('application'))
gateway = None
if not application.category_remote_app:
actions = Action.NONE
remote_app = {}
asset = None
domain = application.domain
else:
remote_app = application.get_rdp_remote_app_setting()
actions = Action.CONNECT
asset = application.get_remote_app_asset()
domain = asset.domain
if domain and domain.has_gateway():
gateway = domain.random_gateway()
return {
'asset': asset,
'application': application,
'gateway': gateway,
'remote_app': remote_app,
'actions': actions
}
@staticmethod
def _get_asset_secret_detail(value, user, system_user):
from assets.models import Asset
from perms.utils.asset import get_asset_system_users_id_with_actions_by_user
asset = get_object_or_404(Asset, id=value.get('asset'))
systemuserid_actions_mapper = get_asset_system_users_id_with_actions_by_user(user, asset)
actions = systemuserid_actions_mapper.get(system_user.id, [])
gateway = None
if asset and asset.domain and asset.domain.has_gateway():
gateway = asset.domain.random_gateway()
return {
'asset': asset,
'application': None,
'gateway': gateway,
'remote_app': None,
'actions': actions,
}
@action(methods=['POST'], detail=False, permission_classes=[IsSuperUserOrAppUser], url_path='secret-info/detail')
def get_secret_detail(self, request, *args, **kwargs):
from users.models import User
from assets.models import SystemUser
token = request.data.get('token', '')
key = self.CACHE_KEY_PREFIX.format(token)
value = cache.get(key, None)
if not value:
return Response(status=404)
user = get_object_or_404(User, id=value.get('user'))
system_user = get_object_or_404(SystemUser, id=value.get('system_user'))
data = dict(user=user, system_user=system_user)
if value.get('type') == 'asset':
asset_detail = self._get_asset_secret_detail(value, user=user, system_user=system_user)
data['type'] = 'asset'
data.update(asset_detail)
else:
app_detail = self._get_application_secret_detail(value)
data['type'] = 'application'
data.update(app_detail)
serializer = self.get_serializer(data)
return Response(data=serializer.data, status=200)
def get_permissions(self):
if self.action == "create":
if self.request.data.get('user', None):
self.permission_classes = (IsSuperUser,)
else:
self.permission_classes = (IsValidUser,)
return super().get_permissions()
def get(self, request): def get(self, request):
token = request.query_params.get('token') token = request.query_params.get('token')
value = cache.get(token, None) key = self.CACHE_KEY_PREFIX.format(token)
value = cache.get(key, None)
if not value: if not value:
return Response('', status=404) return Response('', status=404)

View File

@ -4,14 +4,17 @@ from rest_framework import serializers
from common.utils import get_object_or_none from common.utils import get_object_or_none
from users.models import User from users.models import User
from assets.models import Asset, SystemUser, Gateway
from applications.models import Application
from users.serializers import UserProfileSerializer from users.serializers import UserProfileSerializer
from perms.serializers.asset.permission import ActionsField
from .models import AccessKey, LoginConfirmSetting, SSOToken from .models import AccessKey, LoginConfirmSetting, SSOToken
__all__ = [ __all__ = [
'AccessKeySerializer', 'OtpVerifySerializer', 'BearerTokenSerializer', 'AccessKeySerializer', 'OtpVerifySerializer', 'BearerTokenSerializer',
'MFAChallengeSerializer', 'LoginConfirmSettingSerializer', 'SSOTokenSerializer', 'MFAChallengeSerializer', 'LoginConfirmSettingSerializer', 'SSOTokenSerializer',
'ConnectionTokenSerializer', 'ConnectionTokenSerializer', 'ConnectionTokenSecretSerializer'
] ]
@ -86,9 +89,10 @@ class SSOTokenSerializer(serializers.Serializer):
class ConnectionTokenSerializer(serializers.Serializer): class ConnectionTokenSerializer(serializers.Serializer):
user = serializers.CharField(max_length=128, required=True) user = serializers.CharField(max_length=128, required=False, allow_blank=True)
system_user = serializers.CharField(max_length=128, required=True) system_user = serializers.CharField(max_length=128, required=True)
asset = serializers.CharField(max_length=128, required=True) asset = serializers.CharField(max_length=128, required=False)
application = serializers.CharField(max_length=128, required=False)
@staticmethod @staticmethod
def validate_user(user_id): def validate_user(user_id):
@ -113,3 +117,67 @@ class ConnectionTokenSerializer(serializers.Serializer):
if asset is None: if asset is None:
raise serializers.ValidationError('asset id not exist') raise serializers.ValidationError('asset id not exist')
return asset return asset
@staticmethod
def validate_application(app_id):
from applications.models import Application
app = Application.objects.filter(id=app_id).first()
if app is None:
raise serializers.ValidationError('app id not exist')
return app
def validate(self, attrs):
asset = attrs.get('asset')
application = attrs.get('application')
if not asset and not application:
raise serializers.ValidationError('asset or application required')
if asset and application:
raise serializers.ValidationError('asset and application should only one')
return super().validate(attrs)
class ConnectionTokenUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'name', 'username', 'email']
class ConnectionTokenAssetSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = ['id', 'hostname', 'ip', 'port', 'org_id']
class ConnectionTokenSystemUserSerializer(serializers.ModelSerializer):
class Meta:
model = SystemUser
fields = ['id', 'name', 'username', 'password', 'private_key']
class ConnectionTokenGatewaySerializer(serializers.ModelSerializer):
class Meta:
model = Gateway
fields = ['id', 'ip', 'port', 'username', 'password', 'private_key']
class ConnectionTokenRemoteAppSerializer(serializers.Serializer):
program = serializers.CharField()
working_directory = serializers.CharField()
parameters = serializers.CharField()
class ConnectionTokenApplicationSerializer(serializers.ModelSerializer):
class Meta:
model = Application
fields = ['id', 'name', 'category', 'type']
class ConnectionTokenSecretSerializer(serializers.Serializer):
type = serializers.ChoiceField(choices=[('application', 'Application'), ('asset', 'Asset')])
user = ConnectionTokenUserSerializer(read_only=True)
asset = ConnectionTokenAssetSerializer(read_only=True)
remote_app = ConnectionTokenRemoteAppSerializer(read_only=True)
application = ConnectionTokenApplicationSerializer(read_only=True)
system_user = ConnectionTokenSystemUserSerializer(read_only=True)
gateway = ConnectionTokenGatewaySerializer(read_only=True)
actions = ActionsField()

View File

@ -9,6 +9,7 @@ app_name = 'authentication'
router = DefaultRouter() router = DefaultRouter()
router.register('access-keys', api.AccessKeyViewSet, 'access-key') router.register('access-keys', api.AccessKeyViewSet, 'access-key')
router.register('sso', api.SSOViewSet, 'sso') router.register('sso', api.SSOViewSet, 'sso')
router.register('connection-token', api.UserConnectionTokenViewSet, 'connection-token')
urlpatterns = [ urlpatterns = [
@ -16,8 +17,6 @@ urlpatterns = [
path('auth/', api.TokenCreateApi.as_view(), name='user-auth'), path('auth/', api.TokenCreateApi.as_view(), name='user-auth'),
path('tokens/', api.TokenCreateApi.as_view(), name='auth-token'), path('tokens/', api.TokenCreateApi.as_view(), name='auth-token'),
path('mfa/challenge/', api.MFAChallengeApi.as_view(), name='mfa-challenge'), path('mfa/challenge/', api.MFAChallengeApi.as_view(), name='mfa-challenge'),
path('connection-token/',
api.UserConnectionTokenApi.as_view(), name='connection-token'),
path('otp/verify/', api.UserOtpVerifyApi.as_view(), name='user-otp-verify'), path('otp/verify/', api.UserOtpVerifyApi.as_view(), name='user-otp-verify'),
path('login-confirm-ticket/status/', api.TicketStatusApi.as_view(), name='login-confirm-ticket-status'), path('login-confirm-ticket/status/', api.TicketStatusApi.as_view(), name='login-confirm-ticket-status'),
path('login-confirm-settings/<uuid:user_id>/', api.LoginConfirmSettingUpdateApi.as_view(), name='login-confirm-setting-update') path('login-confirm-settings/<uuid:user_id>/', api.LoginConfirmSettingUpdateApi.as_view(), name='login-confirm-setting-update')

View File

@ -64,7 +64,7 @@ msgstr "名称"
#: perms/serializers/application/user_permission.py:33 #: perms/serializers/application/user_permission.py:33
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:20 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:20
msgid "Category" msgid "Category"
msgstr "类" msgstr "类"
#: applications/models/application.py:15 #: applications/models/application.py:15
#: applications/serializers/application.py:48 assets/models/cmd_filter.py:52 #: applications/serializers/application.py:48 assets/models/cmd_filter.py:52
@ -3160,7 +3160,7 @@ msgstr "关闭"
#: tickets/handler/apply_application.py:55 #: tickets/handler/apply_application.py:55
msgid "Applied category" msgid "Applied category"
msgstr "申请的类" msgstr "申请的类"
#: tickets/handler/apply_application.py:56 #: tickets/handler/apply_application.py:56
msgid "Applied type" msgid "Applied type"
@ -3323,7 +3323,7 @@ msgstr "受理人 (显示名称)"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:24 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:24
msgid "Category display" msgid "Category display"
msgstr "类 (显示名称)" msgstr "类 (显示名称)"
#: tickets/serializers/ticket/meta/ticket_type/apply_application.py:31 #: tickets/serializers/ticket/meta/ticket_type/apply_application.py:31
#: tickets/serializers/ticket/ticket.py:19 #: tickets/serializers/ticket/ticket.py:19

View File

@ -11,6 +11,7 @@ from rest_framework.generics import (
from orgs.utils import tmp_to_root_org from orgs.utils import tmp_to_root_org
from applications.models import Application from applications.models import Application
from perms.utils.application.permission import ( from perms.utils.application.permission import (
has_application_system_permission,
get_application_system_users_id get_application_system_users_id
) )
from perms.api.asset.user_permission.mixin import RoleAdminMixin, RoleUserMixin from perms.api.asset.user_permission.mixin import RoleAdminMixin, RoleUserMixin
@ -71,8 +72,7 @@ class ValidateUserApplicationPermissionApi(APIView):
application = get_object_or_404(Application, id=application_id) application = get_object_or_404(Application, id=application_id)
system_user = get_object_or_404(SystemUser, id=system_user_id) system_user = get_object_or_404(SystemUser, id=system_user_id)
system_users_id = get_application_system_users_id(user, application) if has_application_system_permission(user, application, system_user):
if system_user.id in system_users_id:
return Response({'msg': True}, status=200) return Response({'msg': True}, status=200)
return Response({'msg': False}, status=403) return Response({'msg': False}, status=403)

View File

@ -128,12 +128,10 @@ def on_asset_permission_user_groups_changed(instance, action, pk_set, model,
@receiver(m2m_changed, sender=ApplicationPermission.system_users.through) @receiver(m2m_changed, sender=ApplicationPermission.system_users.through)
def on_application_permission_system_users_changed(sender, instance: ApplicationPermission, action, reverse, pk_set, **kwargs): def on_application_permission_system_users_changed(sender, instance: ApplicationPermission, action, reverse, pk_set, **kwargs):
if not instance.category_remote_app:
return
if reverse: if reverse:
raise M2MReverseNotAllowed raise M2MReverseNotAllowed
if not instance.category_remote_app:
return
if action != POST_ADD: if action != POST_ADD:
return return
@ -156,12 +154,12 @@ def on_application_permission_system_users_changed(sender, instance: Application
@receiver(m2m_changed, sender=ApplicationPermission.users.through) @receiver(m2m_changed, sender=ApplicationPermission.users.through)
def on_application_permission_users_changed(sender, instance, action, reverse, pk_set, **kwargs): def on_application_permission_users_changed(sender, instance, action, reverse, pk_set, **kwargs):
if not instance.category_remote_app:
return
if reverse: if reverse:
raise M2MReverseNotAllowed raise M2MReverseNotAllowed
if not instance.category_remote_app:
return
if action != POST_ADD: if action != POST_ADD:
return return
@ -176,12 +174,10 @@ def on_application_permission_users_changed(sender, instance, action, reverse, p
@receiver(m2m_changed, sender=ApplicationPermission.user_groups.through) @receiver(m2m_changed, sender=ApplicationPermission.user_groups.through)
def on_application_permission_user_groups_changed(sender, instance, action, reverse, pk_set, **kwargs): def on_application_permission_user_groups_changed(sender, instance, action, reverse, pk_set, **kwargs):
if not instance.category_remote_app:
return
if reverse: if reverse:
raise M2MReverseNotAllowed raise M2MReverseNotAllowed
if not instance.category_remote_app:
return
if action != POST_ADD: if action != POST_ADD:
return return
@ -196,12 +192,12 @@ def on_application_permission_user_groups_changed(sender, instance, action, reve
@receiver(m2m_changed, sender=ApplicationPermission.applications.through) @receiver(m2m_changed, sender=ApplicationPermission.applications.through)
def on_application_permission_applications_changed(sender, instance, action, reverse, pk_set, **kwargs): def on_application_permission_applications_changed(sender, instance, action, reverse, pk_set, **kwargs):
if not instance.category_remote_app:
return
if reverse: if reverse:
raise M2MReverseNotAllowed raise M2MReverseNotAllowed
if not instance.category_remote_app:
return
if action != POST_ADD: if action != POST_ADD:
return return

View File

@ -7,8 +7,14 @@ logger = get_logger(__file__)
def get_application_system_users_id(user, application): def get_application_system_users_id(user, application):
queryset = ApplicationPermission.objects\ queryset = ApplicationPermission.objects.valid()\
.filter(Q(users=user) | Q(user_groups__users=user), Q(applications=application))\ .filter(
.valid()\ Q(users=user) | Q(user_groups__users=user),
.values_list('system_users', flat=True) Q(applications=application)
).values_list('system_users', flat=True)
return queryset return queryset
def has_application_system_permission(user, application, system_user):
system_users_id = get_application_system_users_id(user, application)
return system_user.id in system_users_id

View File

@ -4,7 +4,7 @@ from django.db.models import Q
from common.utils import get_logger from common.utils import get_logger
from perms.models import AssetPermission from perms.models import AssetPermission
from perms.hands import Asset, User, UserGroup from perms.hands import Asset, User, UserGroup, SystemUser
from perms.models.base import BasePermissionQuerySet from perms.models.base import BasePermissionQuerySet
logger = get_logger(__file__) logger = get_logger(__file__)
@ -19,10 +19,8 @@ def get_asset_system_users_id_with_actions(asset_perm_queryset: BasePermissionQu
ancestor_keys = node.get_ancestor_keys(with_self=True) ancestor_keys = node.get_ancestor_keys(with_self=True)
node_keys.update(ancestor_keys) node_keys.update(ancestor_keys)
queryset = AssetPermission.objects.filter(id__in=asset_perms_id).filter( queryset = AssetPermission.objects.filter(id__in=asset_perms_id)\
Q(assets=asset) | .filter(Q(assets=asset) | Q(nodes__key__in=node_keys))
Q(nodes__key__in=node_keys)
)
asset_protocols = asset.protocols_as_dict.keys() asset_protocols = asset.protocols_as_dict.keys()
values = queryset.filter( values = queryset.filter(
@ -44,8 +42,14 @@ def get_asset_system_users_id_with_actions_by_user(user: User, asset: Asset):
return get_asset_system_users_id_with_actions(queryset, asset) return get_asset_system_users_id_with_actions(queryset, asset)
def has_asset_system_permission(user: User, asset: Asset, system_user: SystemUser):
systemuser_actions_mapper = get_asset_system_users_id_with_actions_by_user(user, asset)
actions = systemuser_actions_mapper.get(system_user.id, [])
if actions:
return True
return False
def get_asset_system_users_id_with_actions_by_group(group: UserGroup, asset: Asset): def get_asset_system_users_id_with_actions_by_group(group: UserGroup, asset: Asset):
queryset = AssetPermission.objects.filter( queryset = AssetPermission.objects.filter(user_groups=group).valid()
user_groups=group
).valid()
return get_asset_system_users_id_with_actions(queryset, asset) return get_asset_system_users_id_with_actions(queryset, asset)

View File

@ -16,11 +16,10 @@ router.register(r'users', api.UserViewSet, 'user')
router.register(r'groups', api.UserGroupViewSet, 'user-group') router.register(r'groups', api.UserGroupViewSet, 'user-group')
router.register(r'users-groups-relations', api.UserUserGroupRelationViewSet, 'users-groups-relation') router.register(r'users-groups-relations', api.UserUserGroupRelationViewSet, 'users-groups-relation')
router.register(r'service-account-registrations', api.ServiceAccountRegistrationViewSet, 'service-account-registration') router.register(r'service-account-registrations', api.ServiceAccountRegistrationViewSet, 'service-account-registration')
router.register(r'connection-token', auth_api.UserConnectionTokenViewSet, 'connection-token')
urlpatterns = [ urlpatterns = [
path('connection-token/', auth_api.UserConnectionTokenApi.as_view(),
name='connection-token'),
path('profile/', api.UserProfileApi.as_view(), name='user-profile'), path('profile/', api.UserProfileApi.as_view(), name='user-profile'),
path('profile/password/', api.UserPasswordApi.as_view(), name='user-password'), path('profile/password/', api.UserPasswordApi.as_view(), name='user-password'),
path('profile/public-key/', api.UserPublicKeyApi.as_view(), name='user-public-key'), path('profile/public-key/', api.UserPublicKeyApi.as_view(), name='user-public-key'),