增加域名访问权限控制,用户绑定的域名只能允许本用户应用访问

publicsignpoll
youngS 3 years ago
parent f6918011ca
commit 3325814c03
  1. 2
      fir_client/src/components/FirBase.vue
  2. 35
      fir_client/src/components/base/BindDomain.vue
  3. 2
      fir_ser/api/utils/app/supersignutils.py
  4. 13
      fir_ser/api/utils/baseutils.py
  5. 41
      fir_ser/api/utils/storage/caches.py
  6. 2
      fir_ser/api/utils/utils.py
  7. 120
      fir_ser/api/views/domain.py
  8. 43
      fir_ser/api/views/download.py

@ -20,7 +20,7 @@
<div slot="title"> <div slot="title">
<span :underline="false" <span :underline="false"
class="domian-tip-bar">应用分发请绑定您自己的域名平台分发域名可能因不可违因素更换将导致您的应用无法访问</span> class="domian-tip-bar">应用分发请绑定您自己的域名平台分发域名可能因不可违因素更换将导致您的应用无法访问</span>
<el-button size="medium" @click="bind_domain_sure=true">立即绑定</el-button> <el-button size="medium" @click="bind_domain_type=1,bind_domain_sure=true">立即绑定</el-button>
</div> </div>
</el-alert> </el-alert>
</div> </div>

@ -36,12 +36,26 @@
align="center" align="center"
label="主机记录" label="主机记录"
> >
<template slot-scope="scope">
<el-tooltip content="点击复制到剪贴板">
<el-link v-if="scope.row.host" :underline="false" v-clipboard:copy="scope.row.host"
v-clipboard:success="copy_success">{{ scope.row.host }}
</el-link>
</el-tooltip>
</template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
prop="dns" prop="dns"
align="center" align="center"
label="记录值" label="记录值"
width="300"> width="300">
<template slot-scope="scope">
<el-tooltip content="点击复制到剪贴板">
<el-link v-if="scope.row.dns" :underline="false" v-clipboard:copy="scope.row.dns"
v-clipboard:success="copy_success">{{ scope.row.dns }}
</el-link>
</el-tooltip>
</template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-alert title="请在域名DNS配置成功后,点击“下一步”按钮" <el-alert title="请在域名DNS配置成功后,点击“下一步”按钮"
@ -123,7 +137,9 @@
<el-button @click="last" <el-button @click="last"
:disabled="bind_status|| active===1 ">上一步 :disabled="bind_status|| active===1 ">上一步
</el-button> </el-button>
<el-button @click="next">下一步</el-button> <el-button v-if="force_bind" @click="next(1)">强制绑定</el-button>
<el-button v-else @click="next(0)">下一步</el-button>
</div> </div>
</div> </div>
@ -156,6 +172,7 @@
bind_domain_sure: true, bind_domain_sure: true,
domain_name: '', domain_name: '',
domain_tData: [{'type': 'CNAME', 'host': 'xxx', 'dns': 'demo.xxx.cn'}], domain_tData: [{'type': 'CNAME', 'host': 'xxx', 'dns': 'demo.xxx.cn'}],
force_bind:false,
} }
}, },
mounted() { mounted() {
@ -165,13 +182,18 @@
this.bind_domain_sure = false; this.bind_domain_sure = false;
}, },
methods: { methods: {
copy_success() {
this.$message.success('复制剪切板成功');
},
check_cname() { check_cname() {
domainFun(data => { domainFun(data => {
if (data.code === 1000) { if (data.code === 1000) {
if (this.active++ > 2) this.active = 3; if (this.active++ > 2) this.active = 3;
this.bind_status = true; this.bind_status = true;
if (!this.app_id) { if (!this.app_id) {
this.$store.dispatch("dodomainshow", false); if (this.domain_type === 1) {
this.$store.dispatch("dodomainshow", false);
}
} }
} else { } else {
if (data.code === 1004) { if (data.code === 1004) {
@ -234,7 +256,7 @@
} }
this.domain_tData[0].dns = cname_domain; this.domain_tData[0].dns = cname_domain;
}, },
next() { next(force_bind) {
if (this.active === 1) { if (this.active === 1) {
domainFun(data => { domainFun(data => {
if (data.code === 1000) { if (data.code === 1000) {
@ -243,11 +265,14 @@
if (this.active++ > 2) this.active = 3; if (this.active++ > 2) this.active = 3;
} }
} else { } else {
this.$message.error("绑定失败 " + data.msg) this.$message.error("绑定失败 " + data.msg);
if(data.code === 1011){
this.force_bind=true
}
} }
}, { }, {
methods: 'POST', methods: 'POST',
data: {domain_name: this.domain_name, app_id: this.app_id, domain_type: this.domain_type} data: {domain_name: this.domain_name, app_id: this.app_id, domain_type: this.domain_type,force_bind:force_bind}
}) })
} else if (this.active === 2) { } else if (this.active === 2) {
this.check_cname() this.check_cname()

@ -4,7 +4,7 @@
# author: liuyu # author: liuyu
# date: 2020/3/6 # date: 2020/3/6
import uuid, xmltodict, os, re, logging, time import uuid, xmltodict, os, logging, time
import zipfile import zipfile
from api.utils.response import BaseResponse from api.utils.response import BaseResponse

@ -229,3 +229,16 @@ def get_filename_form_file(filename):
f_type = '.ipa' f_type = '.ipa'
return f"{app_obj.name}-{app_release_obj.app_version}-{app_obj.short}{f_type}" return f"{app_obj.name}-{app_release_obj.app_version}-{app_obj.short}{f_type}"
return filename return filename
def check_app_domain_name_access(app_obj, access_domain_name, extra_domain=None):
if app_obj and access_domain_name:
domain_list = []
if extra_domain:
domain_list.append(extra_domain)
app_domain_name = get_app_domain_name(app_obj)
if app_domain_name: domain_list.append(app_domain_name)
user_domain_name = get_user_domain_name(app_obj.user_id)
if user_domain_name: domain_list.append(user_domain_name)
if access_domain_name in domain_list:
return True

@ -13,7 +13,7 @@ from django.utils import timezone
from fir_ser.settings import CACHE_KEY_TEMPLATE, SERVER_DOMAIN, SYNC_CACHE_TO_DATABASE, DEFAULT_MOBILEPROVISION, \ from fir_ser.settings import CACHE_KEY_TEMPLATE, SERVER_DOMAIN, SYNC_CACHE_TO_DATABASE, DEFAULT_MOBILEPROVISION, \
USER_FREE_DOWNLOAD_TIMES, AUTH_USER_FREE_DOWNLOAD_TIMES USER_FREE_DOWNLOAD_TIMES, AUTH_USER_FREE_DOWNLOAD_TIMES
from api.utils.storage.storage import Storage, LocalStorage from api.utils.storage.storage import Storage, LocalStorage
from api.utils.baseutils import get_app_d_count_by_app_id, get_app_domain_name, check_app_password from api.utils.baseutils import get_app_d_count_by_app_id, get_app_domain_name, check_app_password, get_user_domain_name
import logging import logging
from django.db.models import F from django.db.models import F
@ -146,12 +146,16 @@ def del_cache_response_by_short(app_id, udid=''):
del_cache_response_by_short_util(combo_dict.get("short"), combo_dict.get("app_id"), udid) del_cache_response_by_short_util(combo_dict.get("short"), combo_dict.get("app_id"), udid)
def del_cache_response_by_short_util(short, app_id, udid): def del_short_cache(short):
logger.info(f"del_cache_response_by_short short:{short} app_id:{app_id} udid:{udid}")
key = "_".join([CACHE_KEY_TEMPLATE.get("download_short_key"), short, '*']) key = "_".join([CACHE_KEY_TEMPLATE.get("download_short_key"), short, '*'])
for app_download_key in cache.iter_keys(key): for app_download_key in cache.iter_keys(key):
cache.delete(app_download_key) cache.delete(app_download_key)
def del_cache_response_by_short_util(short, app_id, udid):
logger.info(f"del_cache_response_by_short short:{short} app_id:{app_id} udid:{udid}")
del_short_cache(short)
cache.delete("_".join([CACHE_KEY_TEMPLATE.get("app_instance_key"), app_id])) cache.delete("_".join([CACHE_KEY_TEMPLATE.get("app_instance_key"), app_id]))
key = 'ShortDownloadView'.lower() key = 'ShortDownloadView'.lower()
@ -274,16 +278,27 @@ def send_msg_over_limit(act, email):
return limit_cache_util(act, auth_code_key, SYNC_CACHE_TO_DATABASE.get("try_send_msg_over_limit_times")) return limit_cache_util(act, auth_code_key, SYNC_CACHE_TO_DATABASE.get("try_send_msg_over_limit_times"))
def set_default_app_wx_easy(user_obj, only_clean_cache=False): def reset_short_response_cache(user_obj, app_obj=None):
app_obj_lists = Apps.objects.filter(user_id=user_obj) if app_obj is None:
for app_obj in app_obj_lists: app_obj_short_list = [x[0] for x in Apps.objects.filter(user_id=user_obj).values_list('short').all() if x]
if only_clean_cache: else:
del_cache_response_by_short(app_obj.app_id) app_obj_short_list = [app_obj.short]
else: for short in app_obj_short_list:
if not get_app_domain_name(app_obj): del_short_cache(short)
app_obj.wxeasytype = True
app_obj.save(update_fields=['wxeasytype'])
del_cache_response_by_short(app_obj.app_id) def reset_app_wx_easy_type(user_obj, app_obj=None):
if get_user_domain_name(user_obj):
return reset_short_response_cache(user_obj, app_obj)
if app_obj is None:
app_obj_list = Apps.objects.filter(user_id=user_obj).all()
else:
app_obj_list = [app_obj]
for app_obj in app_obj_list:
if not get_app_domain_name(app_obj):
app_obj.wxeasytype = True
app_obj.save(update_fields=['wxeasytype'])
del_short_cache(app_obj.short)
def enable_user_download_times_flag(user_id): def enable_user_download_times_flag(user_id):

@ -7,7 +7,7 @@ import os, datetime, random
import binascii import binascii
from fir_ser.settings import SERVER_DOMAIN, CAPTCHA_LENGTH, MEDIA_ROOT, CACHE_KEY_TEMPLATE from fir_ser.settings import SERVER_DOMAIN, CAPTCHA_LENGTH, MEDIA_ROOT, CACHE_KEY_TEMPLATE
from api.models import APPSuperSignUsedInfo, APPToDeveloper, \ from api.models import APPSuperSignUsedInfo, APPToDeveloper, \
UDIDsyncDeveloper, UserInfo, AppReleaseInfo, AppScreenShot, Token, DeveloperDevicesID, Apps UDIDsyncDeveloper, UserInfo, AppReleaseInfo, AppScreenShot, Token, DeveloperDevicesID
from api.utils.storage.caches import get_app_d_count_by_app_id from api.utils.storage.caches import get_app_d_count_by_app_id
from api.utils.storage.localApi import LocalStorage from api.utils.storage.localApi import LocalStorage
from api.utils.storage.storage import Storage from api.utils.storage.storage import Storage

@ -14,7 +14,7 @@ from rest_framework.response import Response
from api.models import UserDomainInfo, Apps from api.models import UserDomainInfo, Apps
import logging import logging
from api.utils.storage.caches import set_default_app_wx_easy, del_cache_response_by_short from api.utils.storage.caches import del_cache_response_by_short, reset_app_wx_easy_type, reset_short_response_cache
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -31,6 +31,32 @@ def get_domain_filter(request):
return filter_dict return filter_dict
def remove_domain_wx_easy(app_obj, user_obj):
if app_obj and not get_user_domain_name(user_obj):
app_obj.wxeasytype = True
app_obj.save(update_fields=['wxeasytype'])
del_cache_response_by_short(app_obj.app_id)
def add_new_domain_info(res, request, domain_name, domain_type):
min_domain_cname_info_obj = get_min_default_domain_cname_obj(False)
if min_domain_cname_info_obj:
res.data = {'cname_domain': min_domain_cname_info_obj.domain_record}
data_dict = {
'user_id': request.user,
'cname_id': min_domain_cname_info_obj,
'domain_name': domain_name,
'app_id': None,
'domain_type': domain_type
}
app_id = request.data.get("app_id", None)
if app_id:
app_obj = Apps.objects.filter(app_id=app_id).first()
if app_obj:
data_dict['app_id'] = app_obj
UserDomainInfo.objects.create(**data_dict)
class DomainCnameView(APIView): class DomainCnameView(APIView):
authentication_classes = [ExpiringTokenAuthentication, ] authentication_classes = [ExpiringTokenAuthentication, ]
@ -49,6 +75,7 @@ class DomainCnameView(APIView):
res = BaseResponse() res = BaseResponse()
domain_name = request.data.get("domain_name", None) domain_name = request.data.get("domain_name", None)
domain_type = request.data.get('domain_type', 1) domain_type = request.data.get('domain_type', 1)
force_bind = request.data.get('force_bind', 0)
if domain_type not in [x[0] for x in list(UserDomainInfo.domain_type_choices)]: if domain_type not in [x[0] for x in list(UserDomainInfo.domain_type_choices)]:
res.code = 1001 res.code = 1001
res.msg = "绑定失败" res.msg = "绑定失败"
@ -56,33 +83,37 @@ class DomainCnameView(APIView):
if domain_name: if domain_name:
domain_name = domain_name.strip(" ") domain_name = domain_name.strip(" ")
if domain_name and len(domain_name) > 3 and is_valid_domain(domain_name): if domain_name and len(domain_name) > 3 and is_valid_domain(domain_name):
if UserDomainInfo.objects.filter(domain_name=domain_name, is_enable=True, filter_dict = get_domain_filter(request)
domain_type=domain_type).count() != 0: if not force_bind:
res.code = 1001 user_domain_obj = UserDomainInfo.objects.filter(domain_name=domain_name, is_enable=True).first()
res.msg = "该域名已经被绑定,请更换其他域名" if user_domain_obj:
res.code = 1011
if user_domain_obj.user_id.uid == request.user.uid:
app_obj = user_domain_obj.app_id
if app_obj:
res.msg = f"该域名已经被应用 {app_obj.name} 绑定"
elif user_domain_obj.domain_type == 0:
res.msg = f"该域名已经被 应用下载码绑定"
else:
res.msg = f"该域名已经被 预览下载页绑定"
else:
res.msg = "该域名已经被其他用户绑定"
res.msg += ",请更换要绑定的域名,或者进行强制绑定域名"
else:
user_domain_obj = UserDomainInfo.objects.filter(**get_domain_filter(request),
domain_name=domain_name).first()
if user_domain_obj:
res.data = {'cname_domain': user_domain_obj.cname_id.domain_record}
else:
UserDomainInfo.objects.filter(**filter_dict, is_enable=False).delete()
add_new_domain_info(res, request, domain_name, domain_type)
else: else:
user_domain_obj = UserDomainInfo.objects.filter(**get_domain_filter(request), user_domain_obj = UserDomainInfo.objects.filter(**filter_dict,
domain_name=domain_name).first() domain_name=domain_name).first()
if user_domain_obj: if user_domain_obj:
res.data = {'cname_domain': user_domain_obj.cname_id.domain_record} res.data = {'cname_domain': user_domain_obj.cname_id.domain_record}
else: else:
UserDomainInfo.objects.filter(**get_domain_filter(request), is_enable=False).delete() add_new_domain_info(res, request, domain_name, domain_type)
min_domain_cname_info_obj = get_min_default_domain_cname_obj(False)
if min_domain_cname_info_obj:
res.data = {'cname_domain': min_domain_cname_info_obj.domain_record}
data_dict = {
'user_id': request.user,
'cname_id': min_domain_cname_info_obj,
'domain_name': domain_name,
'app_id': None,
'domain_type': domain_type
}
app_id = request.data.get("app_id", None)
if app_id:
app_obj = Apps.objects.filter(app_id=app_id).first()
if app_obj:
data_dict['app_id'] = app_obj
UserDomainInfo.objects.create(**data_dict)
else: else:
res.code = 1002 res.code = 1002
res.msg = "该域名校验失败,请检查" res.msg = "该域名校验失败,请检查"
@ -94,19 +125,26 @@ class DomainCnameView(APIView):
if user_domain_obj: if user_domain_obj:
cname = get_cname_from_domain(user_domain_obj.domain_name) cname = get_cname_from_domain(user_domain_obj.domain_name)
if cname == user_domain_obj.cname_id.domain_record + '.': if cname == user_domain_obj.cname_id.domain_record + '.':
user_domain_obj.is_enable = True user_domain_obj_list = UserDomainInfo.objects.filter(domain_name=user_domain_obj.domain_name,
user_domain_obj.save(update_fields=["is_enable"]) is_enable=True).all()
UserDomainInfo.objects.filter(domain_name=user_domain_obj.domain_name, is_enable=False, if len(user_domain_obj_list) < 2:
domain_type=user_domain_obj.domain_type).delete() if len(user_domain_obj_list) == 1:
app_id = request.data.get("app_id", None) user_obj = user_domain_obj_list.first().user_id
if app_id: app_obj = user_domain_obj_list.first().app_id
app_obj = Apps.objects.filter(app_id=app_id).first() user_domain_obj_list.delete()
if app_obj: reset_app_wx_easy_type(user_obj, app_obj)
app_obj.wxeasytype = False
app_obj.save(update_fields=['wxeasytype']) user_domain_obj.is_enable = True
del_cache_response_by_short(app_obj.app_id) user_domain_obj.save(update_fields=["is_enable"])
app_id = request.data.get("app_id", None)
app_obj = None
if app_id:
app_obj = Apps.objects.filter(app_id=app_id).first()
reset_app_wx_easy_type(request.user, app_obj)
else: else:
set_default_app_wx_easy(request.user, True) res.code = 1002
res.msg = "该域名查询校验失败,请检查"
else: else:
res.code = 1003 res.code = 1003
res.msg = "系统未检出到您的CNAME记录" res.msg = "系统未检出到您的CNAME记录"
@ -117,14 +155,12 @@ class DomainCnameView(APIView):
def delete(sele, request): def delete(sele, request):
res = BaseResponse() res = BaseResponse()
if UserDomainInfo.objects.filter(**get_domain_filter(request)).delete()[0]: user_domain_obj = UserDomainInfo.objects.filter(**get_domain_filter(request)).first()
if user_domain_obj:
app_id = request.query_params.get("app_id", None) app_id = request.query_params.get("app_id", None)
app_obj = None
if app_id: if app_id:
app_obj = Apps.objects.filter(app_id=app_id).first() app_obj = Apps.objects.filter(app_id=app_id).first()
if app_obj and not get_user_domain_name(request.user): reset_app_wx_easy_type(request.user, app_obj)
app_obj.wxeasytype = True user_domain_obj.delete()
app_obj.save(update_fields=['wxeasytype'])
del_cache_response_by_short(app_obj.app_id)
else:
set_default_app_wx_easy(request.user)
return Response(res.dict) return Response(res.dict)

@ -3,6 +3,8 @@
# project: 3月 # project: 3月
# author: liuyu # author: liuyu
# date: 2020/3/6 # date: 2020/3/6
from urllib.parse import urlsplit
from rest_framework.views import APIView from rest_framework.views import APIView
from api.utils.TokenManager import verify_token from api.utils.TokenManager import verify_token
@ -21,7 +23,8 @@ from api.utils.serializer import AppsShortSerializer
from api.models import Apps, AppReleaseInfo, APPToDeveloper, APPSuperSignUsedInfo from api.models import Apps, AppReleaseInfo, APPToDeveloper, APPSuperSignUsedInfo
from django.http import FileResponse from django.http import FileResponse
import logging import logging
from api.utils.baseutils import get_profile_full_path, get_app_domain_name, get_filename_form_file from api.utils.baseutils import get_profile_full_path, get_app_domain_name, get_filename_form_file, \
check_app_domain_name_access
from api.utils.throttle import VisitShortThrottle, InstallShortThrottle, InstallThrottle1, InstallThrottle2 from api.utils.throttle import VisitShortThrottle, InstallShortThrottle, InstallThrottle1, InstallThrottle2
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -146,19 +149,24 @@ class ShortDownloadView(APIView):
res = check_app_permission(app_obj, res) res = check_app_permission(app_obj, res)
if res.code != 1000: if res.code != 1000:
return Response(res.dict) return Response(res.dict)
origin_domain_name = request.META.get('HTTP_ORIGIN', 'xx').split('//')[-1]
domain_name = get_redirect_server_domain(request, app_obj.user_id, get_app_domain_name(app_obj))
if not check_app_domain_name_access(app_obj, origin_domain_name, domain_name.split('//')[-1]):
res.code = 1004
res.msg = "访问域名不合法"
else:
if udid:
if not app_obj.issupersign:
res.code = 1002
res.msg = "参数有误"
return Response(res.dict)
del_cache_response_by_short(app_obj.app_id, udid=udid)
if udid: app_serializer = AppsShortSerializer(app_obj, context={"key": "ShortDownloadView", "release_id": release_id,
if not app_obj.issupersign: "storage": Storage(app_obj.user_id)})
res.code = 1002 res.data = app_serializer.data
res.msg = "参数有误" res.udid = udid
return Response(res.dict) res.domain_name = domain_name
del_cache_response_by_short(app_obj.app_id, udid=udid)
app_serializer = AppsShortSerializer(app_obj, context={"key": "ShortDownloadView", "release_id": release_id,
"storage": Storage(app_obj.user_id)})
res.data = app_serializer.data
res.udid = udid
res.domain_name = get_redirect_server_domain(request, app_obj.user_id, get_app_domain_name(app_obj))
return Response(res.dict) return Response(res.dict)
# key的设置 # key的设置
@ -167,13 +175,18 @@ class ShortDownloadView(APIView):
release_id = request.query_params.get("release_id", '') release_id = request.query_params.get("release_id", '')
udid = request.query_params.get("udid", None) udid = request.query_params.get("udid", None)
time = request.query_params.get("time", None) time = request.query_params.get("time", None)
origin_domain_name = request.META.get('HTTP_ORIGIN', 'xx').split('//')[-1]
if not origin_domain_name:
origin_domain_name = 'default.site'
if udid and time: if udid and time:
udid = time udid = time
if not udid: if not udid:
udid = "" udid = ""
logging.info(f"get or make cache_response short:{kwargs.get('short', '')} release_id:{release_id} udid:{udid}") logging.info(
f"get or make cache_response short:{kwargs.get('short', '')} origin_domain_name:{origin_domain_name} release_id:{release_id} udid:{udid}")
return "_".join( return "_".join(
[settings.CACHE_KEY_TEMPLATE.get("download_short_key"), kwargs.get("short", ''), release_id, udid]) [settings.CACHE_KEY_TEMPLATE.get("download_short_key"), kwargs.get("short", ''), origin_domain_name,
release_id, udid])
class InstallView(APIView): class InstallView(APIView):

Loading…
Cancel
Save