From d51ffa0eb257fa7fe9b5ab3e83b022394138f5de Mon Sep 17 00:00:00 2001 From: vlados31 Date: Sat, 17 Sep 2022 12:01:14 +0300 Subject: [PATCH] Add basic API functions and working account.auth method --- account/models.py | 1 - api/admin.py | 4 ++- api/api_errors.py | 41 ++++++++++++++++++++++ api/api_methods.py | 76 ++++++++++++++++++++++++++++++++++++++++ api/api_utils.py | 29 +++++++++++++++ api/models.py | 55 +++++++++++++++++++++++++++-- api/urls.py | 1 + api/views.py | 33 ++++++++++++++--- templates/api/index.html | 31 ++++++++++++++++ 9 files changed, 262 insertions(+), 9 deletions(-) create mode 100644 api/api_errors.py create mode 100644 api/api_methods.py create mode 100644 api/api_utils.py create mode 100644 templates/api/index.html diff --git a/account/models.py b/account/models.py index 3395ded..51b9681 100644 --- a/account/models.py +++ b/account/models.py @@ -20,7 +20,6 @@ class SiteAccountManager(BaseUserManager): return user def get_by_natural_key(self, email_): - print(email_) return self.get(email=email_) diff --git a/api/admin.py b/api/admin.py index 8c38f3f..ac7bb93 100644 --- a/api/admin.py +++ b/api/admin.py @@ -1,3 +1,5 @@ from django.contrib import admin +from .models import * -# Register your models here. + +admin.site.register(UserToken) diff --git a/api/api_errors.py b/api/api_errors.py new file mode 100644 index 0000000..56c7f25 --- /dev/null +++ b/api/api_errors.py @@ -0,0 +1,41 @@ +import traceback + +# как создавать ошибку +# raise Exception(API_ERROR_XXX, ) + + +API_ERROR_INTERNAL_ERROR = (100, 'internal error') + +API_ERROR_METHOD_NOT_FOUND = (200, 'method not found') +API_ERROR_MISSING_ARGUMENT = (201, 'missing argument') +API_ERROR_UNKNOWN_ARGUMENT = (202, 'unknown argument') +API_ERROR_INVALID_ARGUMENT_TYPE = (203, 'invalid argument type') + +API_ERROR_TOKEN_CREATION = (500, 'token creation error') + +API_ERROR_INVALID_LOGIN = (501, 'invalid login') +API_ERROR_INVALID_PASSWORD = (502, 'invalid password') +API_ERROR_INVALID_TOKEN = (503, 'invalid token') + + +def make_error_object(ex: Exception): + try: + data = { + "error": { + "code": ex.args[0][0], + "message": ex.args[0][1] + } + } + + if len(ex.args) >= 2: + data["error"]["related"] = ex.args[1] + + return data + except Exception: + traceback.print_exc() + return { + "error": { + "code": API_ERROR_INTERNAL_ERROR[0], + "message": API_ERROR_INTERNAL_ERROR[1] + } + } diff --git a/api/api_methods.py b/api/api_methods.py new file mode 100644 index 0000000..d650ed4 --- /dev/null +++ b/api/api_methods.py @@ -0,0 +1,76 @@ +from .api_errors import * +from .api_utils import * +from .models import * + + +def _reqire_access_token(params): + token = api_get_param_str(params, "access_token") + return UserToken.get_user_by_token(token) + + +def make_response(response): + return {"response": response} + + +def account_auth(params): + login = api_get_param_str(params, "login") + password = api_get_param_str(params, "password") + user = UserToken.auth(login, password) + token = UserToken.create_token(user) + + return make_response({"access_token": token.access_token}) + + +def account_register(params): + pass + + +def account_get(params): + user = _reqire_access_token(params) + return make_response({"name": user.name, "surname": user.surname, "email": user.email, "phone": user.phone}) + + +def __make_argument_doc(name, arg_type, description, required=True): + return { + "name": name, + "type": arg_type, + "description": description, + "required": required + } + + +def __make_argument_access_token(): + return __make_argument_doc("access_token", "string", "Токен, выданный методом account.auth") + + +__doc_type_string = "string" + + +api_methods = { + "account.auth": { + "func": account_auth, + "doc": "Аутентификация пользователя", + "params": [ + __make_argument_doc("login", __doc_type_string, "Логин пользователя"), + __make_argument_doc("password", __doc_type_string, "Пароль пользователя"), + ], + "returns": "В случае правильных логина и пароля access_token. В противном случае объект ошибки." + }, + "account.register": { + "func": account_register, + "doc": "Регистрация нового пользователя", + "params": [ + + ], + "returns": "Поля пользователя (name, surname, email, phone)." + }, + + "account.get": { + "func": account_get, + "doc": "Получение информации о пользователе", + "params": [ + __make_argument_access_token() + ], + "returns": "Поля пользователя (name, surname, email, phone)." + }, +} diff --git a/api/api_utils.py b/api/api_utils.py new file mode 100644 index 0000000..9077c48 --- /dev/null +++ b/api/api_utils.py @@ -0,0 +1,29 @@ +from .api_errors import * + + +def __make_invalid_argument_type_error(name, value, except_type): + related = {"param_name": name, "excepted_type": "int", "value": value} + raise Exception(API_ERROR_INVALID_ARGUMENT_TYPE, related) + + +def api_get_param_int(params: dict, name: str, required=True, default=0): + if name in params: + try: + return int(params[name]) + except: + __make_invalid_argument_type_error(name, params[name], "int") + + if required: + raise Exception(API_ERROR_MISSING_ARGUMENT, name) + + return default + + +def api_get_param_str(params: dict, name: str, required=True, default=""): + if name in params: + return params[name] + + if required: + raise Exception(API_ERROR_MISSING_ARGUMENT, name) + + return default diff --git a/api/models.py b/api/models.py index 71a8362..ca88adb 100644 --- a/api/models.py +++ b/api/models.py @@ -1,3 +1,54 @@ -from django.db import models +from datetime import datetime -# Create your models here. +from django.db import models +from account.models import SiteUser + +from hashlib import sha512 +from django.contrib.auth.hashers import check_password + +from .api_errors import * + + +class UserToken(models.Model): + user = models.ForeignKey(SiteUser, on_delete=models.CASCADE) + access_token = models.CharField(max_length=128, editable=False, unique=True) + creation_time = models.DateTimeField(default=datetime.now) + + @staticmethod + def create_token(user: SiteUser): + source = bytearray(user.email + user.password + str(datetime.now()), 'utf-8') + h = sha512(source).hexdigest() + + # чекаем токен в базе + if UserToken.objects.filter(access_token=h).count() != 0: + # по какой-то причине есть, выкидываем исключение + raise Exception(API_ERROR_TOKEN_CREATION) + + token = UserToken(access_token=h, user=user) + token.save() + + print(f"created token {token.access_token[:16]}...") + + return token + + @staticmethod + def auth(login: str, password: str): + user = SiteUser.objects.filter(email=login) + + if len(user) == 0: + raise Exception(API_ERROR_INVALID_LOGIN) + + if not check_password(password, user[0].password): + raise Exception(API_ERROR_INVALID_PASSWORD) + + return user[0] + + @staticmethod + def get_user_by_token(token: str): + t = UserToken.objects.get(access_token=token) + if t is None: + raise Exception(API_ERROR_INVALID_TOKEN) + return t.user + + def __str__(self): + return self.user.email + ": " + self.access_token[:10] + "..." diff --git a/api/urls.py b/api/urls.py index 3689129..b0a9863 100644 --- a/api/urls.py +++ b/api/urls.py @@ -18,6 +18,7 @@ from django.urls import path from . import views urlpatterns = [ + path('', views.view_methods, name='view_methods'), path('', views.call_method, name='call_method') ] diff --git a/api/views.py b/api/views.py index d2ea704..46b9ec6 100644 --- a/api/views.py +++ b/api/views.py @@ -1,12 +1,35 @@ import json +import traceback -from django.http import HttpResponse +from django.shortcuts import render +from django.http import HttpResponse, HttpResponseBadRequest +from .api_methods import api_methods +from .api_errors import * + + +def view_methods(request): + return render(request, 'api/index.html', {'api_methods': api_methods}) def call_method(request, method_name): - tmp = {} - for key in request.GET: - tmp[key] = request.GET[key] - response = HttpResponse(json.dumps({"response": {"method": method_name, "params": tmp}}, ensure_ascii=True)) + if request.method == "GET": + params = request.GET + elif request.method == "POST": + params = request.POST + else: + return HttpResponseBadRequest() + + try: + if method_name in api_methods: + out = api_methods[method_name]["func"](params) + if out is None: + raise Exception(API_ERROR_INTERNAL_ERROR, "method returned null object") + else: + raise Exception(API_ERROR_METHOD_NOT_FOUND) + except Exception as ex: + traceback.print_exc() + out = make_error_object(ex) + + response = HttpResponse(json.dumps(out, ensure_ascii=False)) response.headers["Content-type"] = "application/json" return response diff --git a/templates/api/index.html b/templates/api/index.html new file mode 100644 index 0000000..29ed685 --- /dev/null +++ b/templates/api/index.html @@ -0,0 +1,31 @@ +{% extends 'base.html' %} +{% block title %} Аккаунт | вход {% endblock %} + +{% block content %} +

Список методов API

+ + {% for method in api_methods %} +
+

{{ method }}

+ +
+ Описание +
+ {{ api_methods.method.doc | safe }} +
+
+ +
+ Парамеры +
+ {% for param in api_methods.method.params %} +
+ {{ param }} +
+ {% endfor %} +
+
+
+ {% endfor %} + +{% endblock %}