下载授权码和设备绑定功能,下载页面优化

dependabot/npm_and_yarn/fir_admin/async-2.6.4
nineven 3 years ago
parent 747d2b3dfa
commit 8316a52595
  1. 40
      fir_client/src/components/FirDownload.vue
  2. 40
      fir_client/src/components/ShortDownload.vue
  3. 71
      fir_client/src/components/apps/FirAppInfossecurity.vue
  4. 2
      fir_ser/api/migrations/0005_auto_20220412_1744.py
  5. 2
      fir_ser/api/models.py
  6. 11
      fir_ser/api/utils/modelutils.py
  7. 23
      fir_ser/api/views/apps.py
  8. 39
      fir_ser/common/utils/download.py
  9. 23
      fir_ser/xsign/views/receiveudids.py
  10. 4
      fir_ser/xsign/views/supersign.py

@ -417,7 +417,8 @@ export default {
mobileprovision: '',
ad_info: {ad_pic: '', ad_uri: ''},
task_spend_time: 0,
task_msg: '执行中'
task_msg: '执行中',
err_password: false
}
},
beforeDestroy() {
@ -565,6 +566,7 @@ export default {
} else {
this.$message.success('签名完成,请点击下载安装');
this.wrong = false;
this.binary_download();
}
} else {
if (data.code === 1002) {
@ -600,6 +602,22 @@ export default {
window.location.href = this.mobileprovision;
},
check_msg() {
if (this.currentappinfo.need_password) {
if (this.currentappinfo.issupersign && !this.$route.query.udid) {
this.currentappinfo.need_password = false
}
if (this.$route.query.password && !this.err_password) {
this.currentappinfo.need_password = false
this.password = this.$route.query.password;
this.download()
}
} else {
if (this.$route.query.udid && this.$route.query.task_token) {
this.download()
}
}
},
download() {
if (this.$route.query.udid && this.$route.query.task_token) {
this.wrong = true;
this.msg = '签名处理中,请耐心等待';
@ -612,18 +630,32 @@ export default {
this.show_err_msg(data.result);
} else {
this.wrong = false;
this.currentappinfo.need_password = false
this.binary_download()
}
} else if (data.code === 1006) {
this.msg = data.msg;
this.show_err_msg(data.msg);
this.wrong = false;
this.err_password = true
this.currentappinfo.need_password = true
} else {
this.show_err_msg(data.msg);
// eslint-disable-next-line no-unused-vars
this.timer = setTimeout(data => {
window.location.href = location.href.replace(location.search, '');
}, 3000);
}
}, {
methods: 'POST',
short: this.$route.params.short,
data: {"task_token": this.$route.query.task_token}
data: {"task_token": this.$route.query.task_token, 'password': this.password}
})
} else {
this.binary_download()
}
},
download() {
binary_download() {
if (this.currentappinfo.app_id) {
this.isdownload = true;
getdownloadurl(res => {
@ -693,6 +725,8 @@ export default {
window.location.reload();
}
this.password = '';
this.err_password = true;
this.currentappinfo.need_password = true;
}
}, {
'data': {

@ -406,7 +406,8 @@ export default {
mobileprovision: '',
ad_info: {ad_pic: '', ad_uri: ''},
task_spend_time: 0,
task_msg: '执行中'
task_msg: '执行中',
err_password: false
}
},
beforeDestroy() {
@ -530,6 +531,7 @@ export default {
this.show_err_msg(data.msg);
} else {
this.wrong = false;
this.binary_download();
}
} else {
if (data.code === 1002) {
@ -565,6 +567,22 @@ export default {
window.location.href = this.mobileprovision;
},
check_msg() {
if (this.currentappinfo.need_password) {
if (this.currentappinfo.issupersign && !this.$route.query.udid) {
this.currentappinfo.need_password = false
}
if (this.$route.query.password && !this.err_password) {
this.currentappinfo.need_password = false
this.password = this.$route.query.password;
this.download()
}
} else {
if (this.$route.query.udid && this.$route.query.task_token) {
this.download()
}
}
},
download() {
if (this.$route.query.udid && this.$route.query.task_token) {
this.wrong = true;
this.msg = '签名处理中,请耐心等待';
@ -577,18 +595,32 @@ export default {
this.show_err_msg(data.result);
} else {
this.wrong = false;
this.currentappinfo.need_password = false
this.binary_download()
}
} else if (data.code === 1006) {
this.msg = data.msg;
this.show_err_msg(data.msg);
this.wrong = false;
this.err_password = true;
this.currentappinfo.need_password = true;
} else {
this.show_err_msg(data.msg);
// eslint-disable-next-line no-unused-vars
this.timer = setTimeout(data => {
window.location.href = location.href.replace(location.search, '');
}, 3000);
}
}, {
methods: 'POST',
short: this.$route.params.short,
data: {"task_token": this.$route.query.task_token}
data: {"task_token": this.$route.query.task_token, 'password': this.password}
})
} else {
this.binary_download()
}
},
download() {
binary_download() {
if (this.currentappinfo.app_id) {
this.isdownload = true;
getdownloadurl(res => {
@ -652,6 +684,8 @@ export default {
window.location.reload();
}
this.password = '';
this.err_password = true;
this.currentappinfo.need_password = true;
}
}, {
'data': {

@ -15,7 +15,7 @@
:visible.sync="download_password_sure"
center
title="应用下载授权码设置"
width="800px">
width="900px">
<el-dialog
:visible.sync="makeTokenVisible"
@ -42,10 +42,27 @@
</el-form-item>
<el-form-item label="最大使用次数">
<el-input-number v-model="makeTokenInfo.token_max_used_number" :max="1024" :min="0"></el-input-number>
<el-input-number v-model="makeTokenInfo.token_max_used_number" :disabled="makeTokenInfo.bind_status"
:max="1024"
:min="0"></el-input-number>
<el-tag style="margin-left: 5px">0表示不限使用次数</el-tag>
</el-form-item>
<el-form-item v-if="currentapp.type===1 && currentapp.issupersign" label="是否绑定设备">
<el-tooltip placement="top">
<div slot="content">
<span v-if="makeTokenInfo.bind_status"> 开启绑定设备</span>
<span v-else> 关闭绑定设备</span>
</div>
<el-switch
v-model="makeTokenInfo.bind_status"
:active-value="true"
:inactive-value="false"
active-color="#13ce66"
inactive-color="#ff4949">
</el-switch>
</el-tooltip>
<el-tag style="margin-left: 5px">绑定设备之后二次自动进行设备授权下载应用</el-tag>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="cancelDownloadToken">取消</el-button>
@ -56,8 +73,8 @@
<el-input
v-model="dpwdsearch"
:placeholder="dpwdtitle"
clearable
placeholder="输入下载授权码"
style="width: 30%;margin-right: 30px;margin-bottom: 10px"/>
<el-button icon="el-icon-search" type="primary" @click="handleSearch(1)">
@ -123,13 +140,29 @@
align="center"
label="最大可用次数"
prop="max_limit_count"
width="140">
width="130">
<template slot-scope="scope">
<span v-if="scope.row.max_limit_count === 0">不限次数</span>
<span v-else>{{ scope.row.max_limit_count }}</span>
</template>
</el-table-column>
<div v-if="currentapp.type===1 && currentapp.issupersign">
<el-table-column
align="center"
label="绑定设备"
prop="bind_status"
width="60">
<template slot-scope="scope">
<el-tag v-if="scope.row.bind_status"></el-tag>
<el-tag v-else type="info"></el-tag>
</template>
</el-table-column>
<el-table-column
align="center"
label="设备ID"
prop="bind_udid">
</el-table-column>
</div>
<el-table-column
align="center"
fixed="right"
@ -283,7 +316,8 @@ export default {
tokendisable: false,
pagination: {"currentPage": 1, "total": 0, "pagesize": 10},
dpwdsearch: '',
makeTokenInfo: {token: '', token_length: 6, token_number: 20, token_max_used_number: 0},
dpwdtitle: '输入下载授权码',
makeTokenInfo: {token: '', token_length: 6, token_number: 20, token_max_used_number: 0, bind_status: false},
multipleSelection: []
}
@ -291,7 +325,11 @@ export default {
methods: {
format_copy_text(token) {
let short_full_url = this.currentapp.preview_url + "/" + this.currentapp.short;
return "应用链接:" + short_full_url + " 下载授权码:" + token
if (this.currentapp.type === 1 && this.currentapp.issupersign) {
return "应用下载链接:" + short_full_url + " 下载授权码:" + token
} else {
return "应用下载链接:" + short_full_url + "?password=" + token
}
},
copy_success() {
this.$message.success('复制剪切板成功');
@ -322,7 +360,7 @@ export default {
this.multipleSelection = val;
},
cancelDownloadToken() {
this.makeTokenInfo = {token: '', token_length: 6, token_number: 20, token_max_used_number: 0}
this.makeTokenInfo = {token: '', token_length: 6, token_number: 20, token_max_used_number: 0, bind_status: false}
this.makeTokenVisible = false
},
resetDownloadUsed(info) {
@ -551,12 +589,12 @@ export default {
this.currentapp = this.$store.state.currentapp;
this.set_default_flag();
this.orgcurrentapp = deepCopy(this.currentapp);
// if (!this.currentapp.domain_name || this.currentapp.domain_name.length < 3) {
// if (this.$store.state.userinfo.domain_name && this.$store.state.userinfo.domain_name.length > 3) {
// this.defualt_dtitle = this.$store.state.userinfo.domain_name;
// }
// }
this.setbuttondefault(this.currentapp);
if (this.currentapp.type === 1 && this.currentapp.issupersign) {
this.dpwdtitle = '输入下载授权码或者设备udid'
} else {
this.dpwdtitle = '输入下载授权码'
}
}
},
mounted() {
@ -571,6 +609,11 @@ export default {
},
'makeTokenInfo.token': function () {
this.tokendisable = !!(this.makeTokenInfo.token && this.makeTokenInfo.token.length > 0);
},
'makeTokenInfo.bind_status': function () {
if (this.makeTokenInfo.bind_status) {
this.makeTokenInfo.token_max_used_number = 1;
}
}
}, computed: {}
}

@ -27,6 +27,8 @@ class Migration(migrations.Migration):
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='添加时间')),
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('used_count', models.BigIntegerField(default=0, verbose_name='已经使用次数')),
('bind_status', models.BooleanField(default=False, verbose_name='是否绑定设备udid')),
('bind_udid', models.CharField(blank=True, max_length=64, null=True, verbose_name='设备udid')),
('max_limit_count', models.BigIntegerField(default=0, verbose_name='最大可使用次数,0表示不限制')),
('description', models.CharField(blank=True, default='', max_length=256, verbose_name='备注')),
('app_id',

@ -537,6 +537,8 @@ class AppDownloadToken(models.Model):
create_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")
update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
used_count = models.BigIntegerField(verbose_name="已经使用次数", default=0)
bind_status = models.BooleanField(verbose_name="是否绑定设备udid", default=False)
bind_udid = models.CharField(max_length=64, verbose_name='设备udid', null=True, blank=True)
max_limit_count = models.BigIntegerField(verbose_name="最大可使用次数,0表示不限制", default=0)
description = models.CharField(verbose_name="备注", max_length=256, default='', blank=True)

@ -230,13 +230,20 @@ def get_wx_nickname(openid):
return nickname if nickname else '亲爱哒'
def check_app_access_token(app_id, access_token):
def check_app_access_token(app_id, access_token, only_check, udid):
if access_token:
download_token_obj = AppDownloadToken.objects.filter(app_id__app_id=app_id,
token=access_token.upper()).first()
if download_token_obj:
if download_token_obj.max_limit_count == 0:
return True
if not only_check:
download_token_obj.used_count += 1
if download_token_obj.used_count <= download_token_obj.max_limit_count:
download_token_obj.save(update_fields=['used_count'])
if download_token_obj.bind_status and udid:
download_token_obj.bind_udid = udid
if not only_check:
download_token_obj.save(update_fields=['used_count', 'bind_udid'])
return True
if udid:
return AppDownloadToken.objects.filter(app_id__app_id=app_id, bind_udid=udid).count()

@ -6,7 +6,7 @@
import copy
import logging
from django.db.models import Sum, F
from django.db.models import Sum, F, Q
from django.db.models.functions import Length
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
@ -361,7 +361,7 @@ class AppDownloadTokenView(APIView):
dpwdsearch = request.query_params.get('dpwdsearch')
app_token_queryset = AppDownloadToken.objects.filter(app_id__user_id=request.user, app_id__app_id=app_id)
if dpwdsearch:
app_token_queryset = app_token_queryset.filter(token=dpwdsearch)
app_token_queryset = app_token_queryset.filter(Q(token=dpwdsearch) | Q(bind_udid=dpwdsearch))
page_obj = AppsPageNumber()
app_token_page_serializer = page_obj.paginate_queryset(queryset=app_token_queryset.order_by("-create_time"),
request=request, view=self)
@ -378,11 +378,19 @@ class AppDownloadTokenView(APIView):
token = data.get('token')
token_length = data.get('token_length')
token_number = data.get('token_number')
bind_status = data.get('bind_status')
token_max_used_number = data.get('token_max_used_number')
if not isinstance(bind_status, bool):
bind_status = False
if bind_status:
token_max_used_number = 1
app_obj = Apps.objects.filter(user_id=request.user, app_id=app_id).first()
if token and isinstance(token, str) and len(token) >= 4:
AppDownloadToken.objects.update_or_create(app_id=app_obj, token=token,
defaults={"max_limit_count": token_max_used_number})
defaults={"max_limit_count": token_max_used_number,
"bind_status": bind_status})
else:
if isinstance(token_length, int) and 4 <= token_length <= 32:
pass
@ -401,8 +409,8 @@ class AppDownloadTokenView(APIView):
exist_token=copy.deepcopy(exist_token))
bulk_list = []
for d_token in list(set(make_token_list) - set(exist_token))[:token_number]:
bulk_list.append(
AppDownloadToken(token=d_token, max_limit_count=token_max_used_number, app_id=app_obj))
bulk_list.append(AppDownloadToken(token=d_token, max_limit_count=token_max_used_number, app_id=app_obj,
bind_status=bind_status))
AppDownloadToken.objects.bulk_create(bulk_list)
return Response(res.dict)
@ -413,14 +421,15 @@ class AppDownloadTokenView(APIView):
act = request.data.get('act')
if token is not None:
AppDownloadToken.objects.filter(app_id__user_id=request.user,
app_id__app_id=app_id, token=token).update(used_count=0)
app_id__app_id=app_id, token=token).update(used_count=0, bind_udid=None)
if act:
if act == 'all':
AppDownloadToken.objects.filter(app_id__user_id=request.user, app_id__app_id=app_id).delete()
elif act == 'invalid':
AppDownloadToken.objects.filter(app_id__user_id=request.user,
app_id__app_id=app_id,
used_count__gte=F('max_limit_count'), max_limit_count__gt=0).delete()
used_count__gte=F('max_limit_count'),
max_limit_count__gt=0, bind_udid__isnull=False).delete()
elif act == 'some':
tokens = request.data.get('tokens')
if tokens and isinstance(tokens, list):

@ -34,11 +34,11 @@ def get_download_url_by_cache(app_obj, filename, limit, is_download=True, key=''
else:
local_storage = LocalStorage(**Config.IOS_PMFILE_DOWNLOAD_DOMAIN)
download_url_type = 'plist'
if not udid:
if udid:
return run_xsign_app_download_url(app_obj, udid, download_url_type, limit)
else:
if app_obj.get('issupersign', None):
download_url_type = 'mobileconifg'
else:
return run_xsign_app_download_url(app_obj, udid, download_url_type, limit)
supersign = Config.DEFAULT_MOBILEPROVISION.get("supersign")
mobileconifg = ""
@ -63,17 +63,7 @@ def get_download_url_by_cache(app_obj, filename, limit, is_download=True, key=''
return local_storage.get_download_url(filename.split(".")[0] + "." + download_url_type, limit), mobileconifg
def get_app_instance_by_cache(app_id, password, is_download, limit, udid):
if udid:
app_info = Apps.objects.filter(app_id=app_id).values("pk", 'user_id', 'type', 'need_password', 'issupersign',
'user_id__certification__status').first()
if app_info:
app_info['d_count'] = get_app_d_count_by_app_id(app_id)
need_password = app_info.get("need_password")
if need_password and not is_download:
if not check_app_access_token(app_id, password):
return False, '下载授权码有误'
return True, app_info
def get_app_instance_by_cache(app_id, limit):
app_instance_cache = AppInstanceCache(app_id)
app_obj_cache = app_instance_cache.get_storage_cache()
if not app_obj_cache:
@ -85,11 +75,6 @@ def get_app_instance_by_cache(app_id, password, is_download, limit, udid):
app_instance_cache.set_storage_cache(app_obj_cache, limit)
if not app_obj_cache:
return False, '应用不存在'
need_password = app_obj_cache.get("need_password")
if need_password and not is_download:
if not check_app_access_token(app_id, password):
return False, '下载授权码有误'
return True, app_obj_cache
@ -114,8 +99,16 @@ def set_app_download_by_cache(app_id, limit=900):
return download_times + 1
def check_app_download_token(need_password, is_download, app_id, password, only_check=False, udid=None):
if need_password:
if is_download or check_app_access_token(app_id, password, only_check, udid):
return True
else:
return True
def get_app_download_url(request, res, app_id, short, password, release_id, is_download, udid):
status, app_obj = get_app_instance_by_cache(app_id, password, is_download, 900, udid)
status, app_obj = get_app_instance_by_cache(app_id, 900)
if status:
if app_obj.get("type") == 0:
app_type = '.apk'
@ -127,6 +120,12 @@ def get_app_download_url(request, res, app_id, short, password, release_id, is_d
res.data = {"download_url": download_url, "extra_url": extra_url}
if download_url != "" and "mobileconifg" not in download_url:
if not check_app_download_token(app_obj.get("need_password"), is_download, app_id, password):
res.code = 1006
res.msg = '下载授权码有误'
return res
ip = get_real_ip_address(request)
msg = f"remote ip {ip} short {short} download_url {download_url} app_obj {app_obj}"
logger.info(msg)

@ -13,7 +13,7 @@ from django.views import View
from rest_framework.response import Response
from rest_framework.views import APIView
from api.models import Apps
from api.models import Apps, AppDownloadToken
from api.utils.modelutils import get_redirect_server_domain, add_remote_info_from_request, \
get_app_download_uri
from api.utils.response import BaseResponse
@ -22,6 +22,7 @@ from common.cache.storage import TaskStateCache
from common.core.sysconfig import Config
from common.core.throttle import ReceiveUdidThrottle1, ReceiveUdidThrottle2, VisitShortThrottle, InstallShortThrottle
from common.utils.caches import check_app_permission
from common.utils.download import check_app_download_token
from common.utils.pending import get_pending_result
from common.utils.token import verify_token, make_token
from fir_ser.celery import app
@ -73,6 +74,10 @@ class IosUDIDView(APIView):
}
encrypt_data = AesBaseCrypt().get_encrypt_uid(json.dumps(data))
msg = "&task_token=%s" % quote(encrypt_data, safe='/', encoding=None, errors=None)
token_obj = AppDownloadToken.objects.filter(app_id__app_id=app_id,
bind_udid=format_udid_info.get('udid')).first()
if token_obj:
msg = f"{msg}&password={token_obj.token}"
else:
return HttpResponsePermanentRedirect(f"{server_domain}/{short}")
else:
@ -140,6 +145,7 @@ class TaskView(APIView):
def post(self, request, short):
res = BaseResponse()
task_token = request.data.get('task_token', None)
password = request.data.get('password', None)
client_ip = get_real_ip_address(request)
if task_token:
data = json.loads(AesBaseCrypt().get_decrypt_uid(task_token))
@ -155,21 +161,26 @@ class TaskView(APIView):
res.msg = '错误,数据异常,请重试'
res.code = 1003
if not verify_token(data.get('r_token', ''), app_obj.app_id, True):
res.msg = '非法,数据异常,请重试'
format_udid_info = data.get('format_udid_info')
udid = format_udid_info.get('udid')
if not verify_token(data.get('r_token', ''), app_obj.app_id, False):
res.msg = '授权过期,请重试'
res.code = 1004
if not check_app_download_token(app_obj.need_password, False, app_obj.app_id, password, False, udid):
res.code = 1006
res.msg = '下载授权码有误'
if res.code != 1000:
return Response(res.dict)
format_udid_info = data.get('format_udid_info')
c_task = run_sign_task.apply_async((format_udid_info, short, client_ip))
add_remote_info_from_request(request, f'{app_obj}-{format_udid_info}')
task_id = c_task.id
logger.info(f"sign app {app_obj} task_id:{task_id}")
try:
result = c_task.get(propagate=False, timeout=3)
result = c_task.get(propagate=False, timeout=1)
except TimeoutError:
logger.error(f"get task task_id:{task_id} result timeout")
result = ''

@ -118,7 +118,7 @@ class DeveloperView(APIView):
if devicestatus:
run_queryset = run_queryset.filter(udidsyncdeveloper__status=devicestatus)
for developer_s_obj in run_queryset:
for developer_s_obj in run_queryset.distinct():
pools.submit(run_task, developer_s_obj)
pools.shutdown()
@ -419,7 +419,7 @@ class SuperSignUsedView(APIView):
app_to_dev_obj = APPToDeveloper.objects.filter(app_id=app_obj,
developerid__issuer_id=developer_id).first()
res = get_app_download_url(request, res, app_obj.app_id, app_obj.short, app_obj.password,
res = get_app_download_url(request, res, app_obj.app_id, app_obj.short, None,
app_to_dev_obj.binary_file, True, device_udid)
return Response(res.dict)

Loading…
Cancel
Save