Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V2.5 dev luolu #761

Merged
merged 3 commits into from
Apr 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added api/v1/events/__init__.py
Empty file.
2 changes: 1 addition & 1 deletion api/v1/views/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from . import loginpage
from . import loginpage, auth, extension_config
44 changes: 32 additions & 12 deletions api/v1/views/auth.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,45 @@
from enum import Enum
from logging.config import listen
from typing import Any, Dict, Optional, List
from pydantic import Field
from ninja import Schema, Query, ModelSchema
from arkid.core.event import register_and_dispatch_event
from arkid.core.event import register_event, dispatch_event, Event
from arkid.core.api import api, operation
from arkid.core.models import Tenant
from arkid.core.models import Tenant, ExpiringToken
from arkid.core.translation import gettext_default as _
from arkid.core.token import refresh_token

class AuthTenantSchema(ModelSchema):
class Config:
model = Tenant
model_fields = ['uuid', 'name', 'slug', 'icon']
# validate = False


@api.get("/auth/", response=LoginPageOut, auth=None)
def auth(request, data: LoginPageIn):
class AuthOut(Schema):
data: Dict[str, Optional[Any]]
tenant: AuthTenantSchema


@api.post("/auth/", response=AuthOut, auth=None)
@operation(AuthOut, use_uuid=True)
def auth(request):
tenant_uuid = data.tenant
tenant = Tenant.objects.filter(uuid=tenant_uuid).first()
request.tenant = tenant

request_uuid = request.Meta.get('request_uuid')

# 认证
responses = dispatch_event(Event(tag=data.tag, tenant=tenant, request=request, uuid=request_uuid))
if len(responses) < 1:
return {'error': 'error_code', 'message': '认证插件未启用'}

response = responses[0]
_, (response, _) = response
user = response.get('user')

return {
'tenant': tenant,
'data': {
'login': None,
'password': None,
'register': None,
}
}
# 生成 token
token = refresh_token(user)

return {'data': {'token': token, 'user': user.uuid.hex}}
81 changes: 81 additions & 0 deletions api/v1/views/extension_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from ninja import Schema, ModelSchema
from arkid.core.api import api
from typing import Union
from typing_extensions import Annotated
from pydantic import Field
from arkid.core.extension import ExtensionConfigSchema

class ExtensionConfigSchemaIn(Schema):
config_uuid: str
ext_uuid: str
config: ExtensionConfigSchema

class ExtensionConfigSchemaOut(Schema):
config_uuid: str
ext_uuid: str
ext_name: str
ext_icon: str
config: ExtensionConfigSchema

@api.post("/extension/config", response=ExtensionConfigSchemaOut)
def create_extension_config(request, data: ExtensionConfigSchemaIn):
# extension.save()
# return {"uuid": extension.uuid.hex}
return data


# @api.get("/extensions/{extension_uuid}", response=ExtensionDetailOut)
# def get_extension(request, extension_uuid: str):
# extension = get_object_or_404(Extension, uuid=extension_uuid, user=request.user)
# return extension


# @api.get("/extensions", response=List[ExtensionOut])
# def list_extensions(request, status: str = None):
# if not status:
# qs = Extension.active_objects.filter(user=request.user)
# else:
# qs = Extension.active_objects.filter(user=request.user, status=status)
# return qs


# @operation(roles=["tenant-user", "platform-user"])
# def update_extension(request, extension_uuid: str, payload: ExtensionIn):
# extension = get_object_or_404(Extension, uuid=extension_uuid, user=request.user)
# data = payload.dict()
# file_name = data.pop("file_name")
# categories = data.pop("categories")
# price = data.pop("price")
# price_type = data.pop("price_type")
# cost_discount = data.pop("cost_discount")

# labels = data.pop("labels")
# data["file"] = File.active_objects.filter(name=file_name).first()
# data["user"] = request.user
# data["tenant"] = request.user.tenant
# for attr, value in data.items():
# setattr(extension, attr, value)
# if categories:
# for category in categories:
# category = Category.active_objects.filter(name=category).first()
# if category:
# extension.categories.add(category)
# if price:
# extension.prices.clear()
# price, is_create = Price.objects.get_or_create(
# type=price_type,
# standard_price=price,
# cost_discount=cost_discount,
# )
# extension.prices.add(price)
# if labels:
# extension.label = " ".join(labels)
# extension.save()
# return {"success": True}


# @api.delete("/extensions/{extension_uuid}")
# def delete_extension(request, extension_uuid: str):
# extension = get_object_or_404(Extension, uuid=extension_uuid)
# extension.delete()
# return {"success": True}
68 changes: 47 additions & 21 deletions api/v1/views/loginpage.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
from arkid.core.event import Event, register_event, dispatch_event
from arkid.core.api import api, operation
from arkid.core.models import Tenant
from arkid.core.extension import AuthFactorExtension
from arkid.core.translation import gettext_default as _
from arkid.core.event import CREATE_LOGIN_PAGE_AUTH_FACTOR, CREATE_LOGIN_PAGE_RULES


class LoginPageIn(Schema):
Expand Down Expand Up @@ -40,11 +42,15 @@ class ButtonSchema(Schema):
delay: Optional[int] = Field(title=_('delay','点击后延时(单位:秒)'))
agreement: Optional[ButtonAgreementSchema] = Field(title=_('agreement','隐私声明'))


class LOGIN_FORM_ITEM_TYPES(str, Enum):
text = _('text', '普通文本框')
password = _('password','密码')
hidden = _('hidden','隐藏')


class LoginFormItemSchema(Schema):
value: Any
type: LOGIN_FORM_ITEM_TYPES = Field(title=_('type','种类'))
placeholder: Optional[str] = Field(title=_('placeholder','文字提示'))
name: str = Field(title=_('name','名字'))
Expand Down Expand Up @@ -79,8 +85,8 @@ class LoginPageOut(Schema):
tenant: LoginPageTenantSchema


register_event('CREATE_LOGIN_PAGE_AUTH_FACTOR', '认证因素生成登录页面')
register_event('CREATE_LOGIN_PAGE_RULES', '登录页面生成规则')
register_event(CREATE_LOGIN_PAGE_AUTH_FACTOR, '认证因素生成登录页面')
register_event(CREATE_LOGIN_PAGE_RULES, '登录页面生成规则')


@api.get("/login_page/", response=LoginPageOut, auth=None)
Expand All @@ -91,30 +97,50 @@ def login_page(request, data: LoginPageIn = Query(...)):
request.tenant = tenant

login_pages = []
responses = dispatch_event(Event(tag='CREATE_LOGIN_PAGE_AUTH_FACTOR', tenant=tenant, data={'request': request}))
responses = dispatch_event(Event(tag=CREATE_LOGIN_PAGE_AUTH_FACTOR, tenant=tenant, data={'request': request}))
for _, response in responses:
login_pages.append(response)

dispatch_event(Event(tag='CREATE_LOGIN_PAGE_RULES', tenant=tenant, data={'request': request, 'login_pages': login_pages}))
dispatch_event(Event(tag=CREATE_LOGIN_PAGE_RULES, tenant=tenant, data={'request': request, 'login_pages': login_pages}))

login_page_forms = []
for login_page, extension in login_pages:
login_page_forms.extend(login_page['data']['login']['forms'])
login_forms_submit = {"label": "登录", "http": {"url": "/api/v1/auth/?tenant=tenant_uuid&request_uuid=rqeuset_uuid", "method": "post"}}
register_forms_submit = {}

# page_uuid = uuid.uuid4().hex
data = {}
for login_page, _ in login_pages:
for k,v in login_page['data'].items():
if not data.get(k):
data[k] = v
else:
if not data[k].get('bottoms'):
data[k]['bottoms'] = v.get('bottoms')
else:
data[k]['bottoms'].extend(v.get('bottoms',[]))
if not data[k].get('forms'):
data[k]['forms'] = v.get('forms')
else:
data[k]['forms'].extend(v.get('forms',[]))
if not data[k].get('extend'):
data[k]['extend'] = v.get('extend')
else:
data[k]['extend']['buttons'].extend(v.get('extend', {}).get('buttons', []))
if not data[k].get('name'):
data[k]['name'] = k

if data.get(AuthFactorExtension.RESET_PASSWORD):
bottom = {"label": "忘记密码", "gopage": AuthFactorExtension.RESET_PASSWORD}
data[AuthFactorExtension.LOGIN]['bottoms'].insert(0, bottom)
bottom = {"prepend": "已有账号,", "label": "立即登录", "gopage": AuthFactorExtension.LOGIN}
data[AuthFactorExtension.RESET_PASSWORD]['bottoms'].insert(0, bottom)

if data.get(AuthFactorExtension.REGISTER):
bottom = {"prepend": "还没有账号,", "label": "立即注册", "gopage": AuthFactorExtension.REGISTER}
data[AuthFactorExtension.LOGIN]['bottoms'].insert(0, bottom)
bottom = {"prepend": "已有账号,", "label": "立即登录", "gopage": AuthFactorExtension.LOGIN}
data[AuthFactorExtension.REGISTER]['bottoms'].insert(0, bottom)

if data.get(AuthFactorExtension.REGISTER) and data.get(AuthFactorExtension.RESET_PASSWORD):
bottom = {"prepend": "还没有账号,", "label": "立即注册", "gopage": AuthFactorExtension.REGISTER}
data[AuthFactorExtension.RESET_PASSWORD]['bottoms'].insert(0, bottom)

return {
# 'rquest_uuid': uuid.uuid4().hex,
# 'header': 'requst_id'
# '': 'requst_id'
'tenant': tenant,
'data': {
'login': [],
'password': [],
'register': [],
}
'data': data,
}

# response = register_and_dispatch_event(tag='AUTO_LOGIN', name=_('自动登录'), tenant=tenant)
32 changes: 0 additions & 32 deletions arkid/api.py
Original file line number Diff line number Diff line change
@@ -1,32 +0,0 @@
import functools
from ninja import NinjaAPI
from ninja.security import HttpBearer
from django.conf import settings

from .models import User
from .openapi import get_openapi_schema
from .utils import verify_id_token

config = settings.ARKID_OIDC
client_id = config['CLIENT_ID']
oauth_url = config['OIDC_SERVER_URL'].strip('/')


class GlobalAuth(HttpBearer):
openapi_scheme = 'token'

def authenticate(self, request, token):
try:
user_info = verify_id_token(token, client_id, oauth_url)
request.get('permission_url', id_token, request.path)
user = User.objects.get(user_uuid=user_info['sub_uuid'])
except:
return

request.user = user
return token


api = NinjaAPI(auth=GlobalAuth())

api.get_openapi_schema = functools.partial(get_openapi_schema, api)
33 changes: 16 additions & 17 deletions arkid/core/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,22 +89,20 @@ def authenticate(self, request, token):
return token


api = NinjaAPI(auth=GlobalAuth())

api.get_openapi_schema = functools.partial(get_openapi_schema, api)
class ArkidApi(NinjaAPI):
def create_response(self, request, *args, **kwargs):
response = super().create_response(request, *args, **kwargs)
if request.META.get('request_uuid'):
response.headers['request_uuid'] = request.META.get('request_uuid')
return response


class RequestResponse:
def __init__(self, request, response) -> None:
self.request = request
self._response = response
api = ArkidApi(auth=GlobalAuth())

@property
def response(self):
return self._response
api.get_openapi_schema = functools.partial(get_openapi_schema, api)


def operation(respnose_model):
def operation(respnose_model, use_uuid=False):
from functools import partial

class ApiEventData(Schema):
Expand All @@ -127,14 +125,15 @@ def replace_view_func(operation):

old_view_func = operation.view_func
def func(request, *params, **kwargs):
# if not request.header.get('request_uuid'):
# request.header['request_uuid'] = uuid.uuid4().hex
# 插件存 request_uuid
dispatch_event(Event(tag+'_pre', request.tenant, request))
request_uuid = request.Meta.get('request_uuid')
if not request_uuid and use_uuid:
request_uuid = uuid.uuid4().hex
request.Meta['request_uuid'] = request_uuid

dispatch_event(Event(tag+'_pre', request.tenant, request=request, uuid=request_uuid))
response = old_view_func(request=request, *params, **kwargs)
# copy request
dispatch_event(Event(tag, request.tenant, RequestResponse(request, response)))
print(response)
dispatch_event(Event(tag, request.tenant, request=request, response=response, uuid=request_uuid))
# response 设置 header
# 前端拿到response header request_uuid 存储到内存,后续请求都带上request_uuid header
# session
Expand Down
48 changes: 48 additions & 0 deletions arkid/core/error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from enum import Enum


class ErrorCode(Enum):

OK = '0'
USERNAME_PASSWORD_MISMATCH = '10001'
SMS_CODE_MISMATCH = '10002'
EMAIL_CODE_MISMATCH = '10021'
USERNAME_EXISTS_ERROR = '10004'
PASSWORD_NONE_ERROR = '10005'
PASSWORD_STRENGTH_ERROR = '10006'

TENANT_NO_ACCESS = '10003'
TENANT_NO_EXISTS = '10007'
CODE_EXISTS_ERROR = '10008'
CODE_FILENAME_EXISTS_ERROR = '10009'
AUTHCODE_ERROR = '10010'
OLD_PASSWORD_ERROR = '10011'
USER_EXISTS_ERROR = '10012'
SLUG_EXISTS_ERROR = '10013'
REGISTER_FAST_ERROR = '10014'
URI_FROMAT_ERROR = '10015'
FILE_FROMAT_ERROR = '10016'
DATA_PATH_ERROR = '10017'
EMAIL_FROMAT_ERROR = '10018'
MOBILE_FROMAT_ERROR = '10019'
PASSWORD_CHECK_ERROR = '10020'
MOBILE_NOT_EXISTS_ERROR = '10021'
MOBILE_EXISTS_ERROR = '10022'
EMAIL_NOT_EXISTS_ERROR = '10023'
EMAIL_EXISTS_ERROR = '10024'
QUERY_PARAM_ERROR = '10025'
DUPLICATED_RECORD_ERROR = '10026'
POST_DATA_ERROR = '10027'
PROVIDER_NOT_EXISTS_ERROR = '10028'
PASSWORD_EXPIRED_ERROR = '10029'

SMS_PROVIDER_IS_MISSING = '11001'
AUTHCODE_PROVIDER_IS_MISSING = '11002'
LOCAL_STORAGE_PROVIDER_IS_MISSING = '11003'

USER_IMPORT_ERROR = '12001'
GROUP_IMPORT_ERROR = '12002'
MOBILE_ERROR = '12003'
EMAIL_ERROR = '12004'

ADD_AUTH_TMPL_ERROR = '13001'
Loading