优化代码,增加证书信息展示功能,增加包重签功能

pull/37/head
nineven 3 years ago
parent cf9d9eeaf8
commit 5edf7cd65a
  1. 115
      fir_client/src/components/user/FirSuperSignBase.vue
  2. 7
      fir_ser/api/views/login_wx.py
  3. 8
      fir_ser/common/base/magic.py
  4. 4
      fir_ser/common/libs/apple/appleapiv3.py
  5. 5
      fir_ser/common/libs/mp/wechat.py
  6. 10
      fir_ser/xsign/tasks.py
  7. 16
      fir_ser/xsign/utils/iossignapi.py
  8. 66
      fir_ser/xsign/utils/supersignutils.py
  9. 33
      fir_ser/xsign/views/supersign.py

@ -106,7 +106,7 @@
<el-form-item label="维护模式" label-width="110px" style="text-align: left">
<el-switch
v-model="read_only_mode"
:disabled="read_only_mode==='on'"
:disabled="editdeveloperinfo.status !== 1"
active-color="#13ce66"
active-value="on"
inactive-color="#ff4949"
@ -145,6 +145,9 @@
<el-button v-if="isedit && editdeveloperinfo.certid" size="small"
@click="exportcert">导出证书
</el-button>
<el-button v-if="isedit && editdeveloperinfo.status!==0 || editdeveloperinfo.certid" size="small"
@click="checkcert">证书检测
</el-button>
<el-button v-if="isedit && !editdeveloperinfo.certid" size="small"
@click="importcertDeveloperVisible=true">导入p12证书
</el-button>
@ -325,6 +328,68 @@
</el-row>
</div>
</el-dialog>
<el-dialog :close-on-click-modal="false" :destroy-on-close="true" :visible.sync="certinfovisible"
center title="苹果开发证书信息" width="750px">
<el-tag style="margin: 10px">期望的证书ID{{ editdeveloperinfo.certid }}</el-tag>
<el-tag v-if="certinfo_err" style="margin: 10px" type="danger">{{ certinfo_err }}</el-tag>
<el-table
v-loading="loading"
:data="certinfo"
border
stripe
style="width: 100%">
<el-table-column
align="center"
label="证书ID"
prop="certid"
>
<template slot-scope="scope">
<el-popover placement="top" trigger="hover">
<p>证书ID: {{ scope.row.certid }}</p>
<p>证书序列号: {{ scope.row.serial_number }}</p>
<p>证书平台: {{ scope.row.platform }}</p>
<p>证书名称: {{ scope.row.name }}</p>
<p>证书显示名字: {{ scope.row.display_name }}</p>
<div slot="reference" class="name-wrapper">
<el-tag>{{ scope.row.certid }}</el-tag>
</div>
</el-popover>
</template>
</el-table-column>
<el-table-column
align="center"
label="证书类型"
prop="c_type"
>
</el-table-column>
<el-table-column
align="center"
label="是否本属于本平台"
prop="c_type"
>
<template slot-scope="scope">
<el-tag v-if="scope.row.exist">属于</el-tag>
<el-tag v-else type="info">
其他
</el-tag>
</template>
</el-table-column>
<el-table-column
:formatter="formatter"
align="center"
label="到期时间"
prop="expire"
width="160">
</el-table-column>
</el-table>
<span slot="footer">
<el-button @click="certinfovisible=false,certinfo=[]">关闭</el-button>
</span>
</el-dialog>
<el-tabs v-model="activeName" tab-position="top" type="border-card" @tab-click="handleClick">
@ -519,6 +584,7 @@
align="center"
label="设备消耗"
prop="use_number"
sortable
width="60">
<template slot-scope="scope">
<el-popover placement="top" trigger="hover">
@ -535,6 +601,7 @@
align="center"
label="应用签名"
prop="app_used_count"
sortable
width="60">
<template slot-scope="scope">
@ -555,7 +622,8 @@
<el-table-column
align="center"
label="专属账户"
prop="is_private"
prop="app_private_number"
sortable
width="60">
<template slot-scope="scope">
<el-popover placement="top"
@ -579,6 +647,7 @@
align="center"
label="自动检测"
prop="auto_check"
sortable
width="60">
<template slot-scope="scope">
<el-tag v-if="scope.row.auto_check" size="medium"></el-tag>
@ -598,6 +667,7 @@
align="center"
label="证书到期时间"
prop="cert_expire_time"
sortable
width="100">
</el-table-column>
<el-table-column
@ -985,6 +1055,11 @@
<p>开发者ID: {{ get_developer_uid(scope.row.issuer_id) }}</p>
<p>开发者备注: {{ scope.row.developer_description }}</p>
<p>开发者状态: {{ scope.row.developer_status }}</p>
<p>安装异常???
<el-button :disabled="scope.row.developer_status!=='已激活'" size="small" @click="resign(scope.row)">
尝试重新签包
</el-button>
</p>
<div slot="reference" class="name-wrapper">
<span>{{ get_developer_uid(scope.row.issuer_id) }}</span>
</div>
@ -1315,6 +1390,9 @@ export default {
components: {AppleDeveloperBindApp},
data() {
return {
certinfovisible: false,
certinfo: [],
certinfo_err: '',
dialogShowDeviceBillInfo: false,
currentudid: '',
balance_info: {all_balance: 0, used_balance: 0},
@ -1692,6 +1770,18 @@ export default {
return '#F50346';
}
},
resign(row_info) {
let other_uid = row_info.other_uid
let uid = ''
if (other_uid && other_uid.uid) {
uid = other_uid.uid
}
this.iosdevicesudidFun('POST', {
id: row_info.id,
aid: row_info.app_id,
uid: uid
}, null)
},
udidDeleteFun(scope, disabled) {
this.$confirm('此操作会禁用该苹果开发者账户下面的该设备,可能会导致超级签包的闪退, 是否继续?', '警告', {
confirmButtonText: '确定',
@ -1768,6 +1858,12 @@ export default {
developercert(data => {
}, {methods: 'FILE', data: {issuer_id: this.editdeveloperinfo.issuer_id}})
},
checkcert() {
this.iosdeveloperFun({
"methods": "PUT",
"data": {"issuer_id": this.editdeveloperinfo.issuer_id, "act": 'checkcert'}
});
},
isorenewcert(act) {
this.$confirm('此操作将永久删除该发布证书, 建议先导出证书。是否继续删除?', '提示', {
confirmButtonText: '确定',
@ -1943,7 +2039,7 @@ export default {
} else {
this.loading = true
}
if (params.methods === 'PUT') {
if (params.methods === 'PUT' && params.data.act !== 'checkcert') {
params.data.size = this.pagination.pagesize
params.data.page = this.pagination.currentPage
}
@ -1973,6 +2069,13 @@ export default {
this.setdeveloperstatusVisible = false
this.change_developer_status = ''
}
if (params.data.act === 'checkcert') {
this.certinfo = data.data.cert_info;
this.certinfo_err = data.data.return_info;
this.loadingfun.close();
this.certinfovisible = true
return
}
if (this.dialogaddDeveloperVisible) {
this.canceledit();
this.$message.success("操作成功");
@ -2071,13 +2174,15 @@ export default {
}
iosdevicesudid(data => {
if (data.code === 1000) {
if (action !== "DELETE") {
if (action === "GET") {
this.app_udid_lists = data.data;
this.pagination.total = data.count;
} else {
} else if (action === "DELETE") {
if (scope) {
this.app_udid_lists = removeAaary(this.app_udid_lists, scope.row)
}
} else if (action === 'POST') {
this.$message.success("操作成功");
}
} else {
this.$message.error("操作失败了 " + data.msg);

@ -1,5 +1,6 @@
import logging
from django.http import HttpResponse
from rest_framework.response import Response
from rest_framework.views import APIView
@ -134,13 +135,13 @@ class WeChatWebLoginView(APIView):
'openid': wx_user_info.get('openid'),
'nickname': wx_user_info.get('nickname'),
'sex': wx_user_info.get('sex'),
'subscribe_time': wx_user_info.get('subscribe_time', 0),
# 'subscribe_time': wx_user_info.get('subscribe_time', 0),
'head_img_url': wx_user_info.get('headimgurl', ''),
'address': f"{wx_user_info.get('country')}-{wx_user_info.get('province')}-{wx_user_info.get('city')}",
'subscribe': wx_user_info.get('subscribe', 0),
# 'subscribe': wx_user_info.get('subscribe', 0),
}
logger.info(f'{wx_user_info}')
ThirdWeChatUserInfo.objects.filter(openid=wx_user_info.get('openid')).update(**wx_user_info)
return Response('更新成功')
return HttpResponse('<h2>更新成功</h2>')
ret.data = wx_login_obj.make_auth_uri()
return Response(ret.dict)

@ -64,3 +64,11 @@ def call_function_try_attempts(try_attempts=3, sleep_time=2, failed_callback=Non
return wrapper
return decorator
def magic_wrapper(func, *args, **kwargs):
@wraps(func)
def wrapper():
return func(*args, **kwargs)
return wrapper

@ -870,8 +870,8 @@ class AppStoreConnectApi(DevicesAPI, BundleIDsAPI, BundleIDsCapabilityAPI, Profi
return self.__profile_store(req)
@call_function_try_attempts(try_attempts=2)
def get_all_certificates(self):
req = self.list_certificate()
def get_all_certificates(self, query_parameters=None):
req = self.list_certificate(query_parameters)
return self.__certificates_store(req)
@call_function_try_attempts()

@ -27,6 +27,7 @@ def format_req_json(j_data, func, *args, **kwargs):
if not status:
return result
return func(*args, **kwargs)[1]
logger.info(f'j_data:{j_data}')
return j_data
@ -174,6 +175,8 @@ class WxMsgCrypt(WxMsgCryptBase):
class WxTemplateMsg(object):
def send_msg(self, to_user, template_id, content):
if not Config.THIRDLOGINCONF.get('active'):
return False, f'weixin status is disabled'
msg_uri = f'https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={get_wx_access_token_cache()}'
data = {
"touser": to_user,
@ -184,7 +187,7 @@ class WxTemplateMsg(object):
}
req = requests.post(msg_uri, json=data)
if req.status_code == 200:
return True, format_req_json(req.json(), get_userinfo_from_openid, to_user)
return True, format_req_json(req.json(), self.send_msg, to_user, template_id, content)
logger.error(f"send msg from openid failed {req.status_code} {req.text}")
return False, req.text

@ -48,12 +48,18 @@ def run_sign_task(format_udid_info, short, client_ip):
return msg
def run_resign_task(app_id, need_download_profile=True, force=True):
def run_resign_task(app_id, need_download_profile=True, force=True, developers_filter=None):
if developers_filter is None:
developers_filter = []
app_obj = Apps.objects.filter(pk=app_id).first()
if app_obj.issupersign and app_obj.user_id.supersign_active:
developer_app_id_queryset = DeveloperAppID.objects.filter(app_id=app_obj)
if developers_filter:
developer_app_id_queryset = developer_app_id_queryset.filter(developerid__in=developers_filter)
with cache.lock("%s_%s" % ('task_resign', app_obj.app_id), timeout=60 * 60):
task_list = []
for developer_app_id_obj in DeveloperAppID.objects.filter(app_id=app_obj).all():
for developer_app_id_obj in developer_app_id_queryset.all():
c_task = run_resign_task_do.apply_async((app_id, developer_app_id_obj.developerid.pk,
developer_app_id_obj.aid, need_download_profile, force))
task_list.append(c_task)

@ -254,7 +254,7 @@ class AppDeveloperApiV2(object):
attr = object.__getattribute__(self, name)
if hasattr(attr, '__call__'):
def func(*args, **kwargs):
if attr.__name__ in ['active', 'get_device', '__result_format', '__callback_run']:
if attr.__name__ in ['get_developer_cert_info', 'get_device', '__result_format', '__callback_run']:
return attr(*args, **kwargs)
else:
if AppIOSDeveloperInfo.objects.filter(pk=self.developer_pk,
@ -313,14 +313,14 @@ class AppDeveloperApiV2(object):
logger.warning(
f'issuer_id:{self.issuer_id} {callback_info}-{failed_call_prefix} is running')
def active(self):
def get_developer_cert_info(self, query_parameters=None):
"""
:return: 结果为空列表或者是 object 或者是 [object,object] 其他为 false
"""
result = {}
try:
apple_obj = AppStoreConnectApi(self.issuer_id, self.private_key_id, self.p8key)
certificates = apple_obj.get_all_certificates()
certificates = apple_obj.get_all_certificates(query_parameters)
return self.__result_format(certificates, Certificates)
except Exception as e:
logger.error(f"issuer_id:{self.issuer_id} ios developer active Failed Exception:{e}")
@ -392,16 +392,18 @@ class AppDeveloperApiV2(object):
cer = load_certificate(FILETYPE_PEM, open(app_dev_pem, 'rb').read())
not_after = datetime.datetime.strptime(cer.get_notAfter().decode('utf-8'), "%Y%m%d%H%M%SZ")
apple_obj = AppStoreConnectApi(self.issuer_id, self.private_key_id, self.p8key)
certificates = apple_obj.get_all_certificates()
certificates = apple_obj.get_all_certificates({'filter[certificateType]': 'IOS_DISTRIBUTION'})
status, result = self.__result_format(certificates, Certificates)
if status:
for cert_obj in result:
f_date = format_apple_date(cert_obj.expirationDate)
logger.info(
f"issuer_id:{self.issuer_id} {cert_obj.id}-{not_after.timestamp()} - {f_date.timestamp()} ")
if not_after.timestamp() == f_date.timestamp():
f"issuer_id:{self.issuer_id} {cert_obj.id}-{not_after.timestamp()} - {f_date.timestamp()} - {cer.get_serial_number()} - {cert_obj.serialNumber} ")
# if not_after.timestamp() == f_date.timestamp(): # 比较证书的序列号来判断是否为同一个证书
if cer.get_serial_number() == int(cert_obj.serialNumber, 16):
return True, cert_obj
raise Exception(str(certificates))
result = {}
raise Exception(str('证书不匹配,请更换其他开发证书重新导入'))
except Exception as e:
logger.error(f"issuer_id:{self.issuer_id} ios developer cert {app_dev_pem} auto get Failed Exception:{e}")
result['return_info'] = check_error_call_back(str(e), self.developer_pk)

@ -9,7 +9,6 @@ import os
import time
import uuid
import zipfile
from functools import wraps
import xmltodict
from django.core.cache import cache
@ -20,7 +19,7 @@ from api.utils.response import BaseResponse
from api.utils.utils import delete_local_files, download_files_form_oss
from common.base.baseutils import file_format_path, delete_app_profile_file, get_profile_full_path, format_apple_date, \
get_format_time, make_app_uuid, make_from_user_uuid
from common.base.magic import run_function_by_locker, call_function_try_attempts
from common.base.magic import run_function_by_locker, call_function_try_attempts, magic_wrapper
from common.cache.state import CleanErrorBundleIdSignDataState
from common.core.sysconfig import Config
from common.utils.caches import del_cache_response_by_short, send_msg_over_limit, check_app_permission, \
@ -214,14 +213,6 @@ def get_apple_udid_key(auth):
return m_key
def err_callback(func, *args, **kwargs):
@wraps(func)
def wrapper():
return func(*args, **kwargs)
return wrapper
def disable_developer_and_send_email(app_obj, developer_obj):
logger.error(f"app {app_obj} developer {developer_obj} sign failed. so disabled")
developer_obj.status = 5
@ -579,7 +570,7 @@ class IosUtils(object):
register_failed_callback = [
{
'func_list': [err_callback(IosUtils.get_device_from_developer, developer_obj)],
'func_list': [magic_wrapper(IosUtils.get_device_from_developer, developer_obj)],
'err_match_msg': [
"There are no current ios devices",
"Your development team has reached the maximum number of registered iPhone devices"
@ -588,14 +579,14 @@ class IosUtils(object):
]
set_failed_callback = [
{
'func_list': [err_callback(IosUtils.get_device_from_developer, developer_obj)],
'func_list': [magic_wrapper(IosUtils.get_device_from_developer, developer_obj)],
'err_match_msg': [
"There are no current ios devices",
"Device obj is None"
]
},
{
'func_list': [err_callback(IosUtils.check_device_status, developer_obj)],
'func_list': [magic_wrapper(IosUtils.check_device_status, developer_obj)],
'err_match_msg': [
"ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE",
]
@ -672,7 +663,7 @@ class IosUtils(object):
failed_callback = []
failed_callback.extend([
{
'func_list': [err_callback(IosUtils.clean_super_sign_things_by_app_obj, app_obj, developer_obj)],
'func_list': [magic_wrapper(IosUtils.clean_super_sign_things_by_app_obj, app_obj, developer_obj)],
'err_match_msg': ["There is no App ID with ID"]
}
])
@ -719,15 +710,15 @@ class IosUtils(object):
failed_callback.extend([
{
'func_list': [err_callback(IosUtils.clean_super_sign_things_by_app_obj, app_obj, developer_obj)],
'func_list': [magic_wrapper(IosUtils.clean_super_sign_things_by_app_obj, app_obj, developer_obj)],
'err_match_msg': ["There is no App ID with ID"]
},
{
'func_list': [err_callback(IosUtils.active_developer, developer_obj, True)],
'func_list': [magic_wrapper(IosUtils.active_developer, developer_obj)],
'err_match_msg': ["There are no current certificates on this team matching the provided certificate"]
},
{
'func_list': [err_callback(IosUtils.check_device_status, developer_obj)],
'func_list': [magic_wrapper(IosUtils.check_device_status, developer_obj)],
'err_match_msg': ["There are no current ios devices on this team matching the provided device IDs"]
}
])
@ -1052,6 +1043,43 @@ class IosUtils(object):
except Exception as e:
return False, {'err_info': str(e)}
@staticmethod
def get_developer_cert_info(developer_obj):
"""
获取开发者证书信息
:param developer_obj:
:return:
"""
app_api_obj = get_api_obj(developer_obj)
# status, result = app_api_obj.get_developer_cert_info({'filter[certificateType]': 'IOS_DISTRIBUTION'})
status, result = app_api_obj.get_developer_cert_info()
if status:
cert_info = []
cert_is_exists = True
for cert_obj in result:
c_info = {
'certid': cert_obj.id,
'serial_number': cert_obj.serialNumber,
'name': cert_obj.name,
'display_name': cert_obj.displayName,
'platform': cert_obj.platform,
'cert_expire_time': format_apple_date(cert_obj.expirationDate),
'c_type': cert_obj.certificateType,
'exist': False}
if cert_obj.id == developer_obj.certid and cert_is_exists:
developer_obj.cert_expire_time = format_apple_date(cert_obj.expirationDate)
cert_is_exists = False
c_info['exist'] = True
cert_info.append(c_info)
cert_info.sort(key=lambda x: x['exist'] == False)
if developer_obj.certid and len(developer_obj.certid) > 3 and cert_is_exists and len(result) > 0:
# 多次判断数据库证书id和苹果开发id不一致,可认为被用户删掉,需要执行清理开发者操作
return status, {'return_info': '证书异常,苹果开发者证书与平台记录ID不一致', 'cert_info': cert_info}
if developer_obj.certid and len(developer_obj.certid) > 3 and len(result) == 0:
return status, {'return_info': '证书异常,苹果开发者证书为空,疑似证书在苹果开发平台被删除', 'cert_info': cert_info}
return status, {'cert_info': cert_info}
return status, result
@staticmethod
def active_developer(developer_obj, auto_clean=True, loop_count=1):
"""
@ -1062,7 +1090,7 @@ class IosUtils(object):
:return:
"""
app_api_obj = get_api_obj(developer_obj)
status, result = app_api_obj.active()
status, result = app_api_obj.get_developer_cert_info({'filter[certificateType]': 'IOS_DISTRIBUTION'})
if status:
cert_is_exists = True
for cert_obj in result:
@ -1091,6 +1119,8 @@ class IosUtils(object):
f"{developer_obj} loop check developer cert.auto_clean:{auto_clean} loop_count:{loop_count}")
return IosUtils.active_developer(developer_obj, auto_clean, loop_count)
if developer_obj.certid and len(developer_obj.certid) > 3 and len(result) == 0:
return False, {'return_info': '证书异常,苹果开发者证书为空,疑似证书在苹果开发平台被删除'}
developer_obj.save(update_fields=['certid', 'cert_expire_time', 'status'])
return status, result

@ -23,7 +23,7 @@ from common.utils.download import get_app_download_url
from xsign.models import AppIOSDeveloperInfo, APPSuperSignUsedInfo, AppUDID, IosDeveloperPublicPoolBill, \
UDIDsyncDeveloper, AppleDeveloperToAppUse, DeveloperAppID, APPToDeveloper, DeveloperDevicesID, \
IosDeveloperBill
from xsign.tasks import run_resign_task_do
from xsign.tasks import run_resign_task_do, run_resign_task
from xsign.utils.modelutils import get_user_public_used_sign_num, get_user_public_sign_num, check_uid_has_relevant, \
get_developer_devices
from xsign.utils.serializer import DeveloperSerializer, SuperSignUsedSerializer, DeviceUDIDSerializer, \
@ -145,6 +145,15 @@ class DeveloperView(APIView):
res.code = 1008
res.msg = result.get("return_info")
return Response(res.dict)
elif act == "checkcert":
if developer_obj.certid:
status, result = IosUtils.get_developer_cert_info(developer_obj)
if status:
res.data = result
else:
res.code = 1008
res.msg = result.get("return_info")
return Response(res.dict)
elif act in ["renewcert", "cleancert"]:
if developer_obj.certid:
# clean developer somethings. remove profile and revoke cert
@ -408,6 +417,24 @@ class AppUDIDUsedView(APIView):
res.msg = '数据异常,删除失败'
return Response(res.dict)
def post(self, request):
res = BaseResponse()
pk = request.data.get("id", None)
other_uid = request.data.get("uid", None)
app_id = request.data.get("aid", None)
app_udid_obj = AppUDID.objects.filter(pk=pk, app_id__user_id=request.user)
if not app_udid_obj and check_uid_has_relevant(request.user.uid, other_uid):
app_udid_obj = AppUDID.objects.filter(pk=pk, app_id__user_id__uid=other_uid)
if app_udid_obj:
super_sign_used_obj = APPSuperSignUsedInfo.objects.filter(udid=app_udid_obj.first()).first()
if super_sign_used_obj:
run_resign_task(app_id, need_download_profile=True, force=True,
developers_filter=[super_sign_used_obj.developerid])
else:
res.code = 10003
res.msg = '数据异常,重签名失败'
return Response(res.dict)
class DeveloperDeviceView(APIView):
authentication_classes = [ExpiringTokenAuthentication, ]
@ -491,12 +518,12 @@ class SuperSignCertView(APIView):
status, result = resign_app_obj.make_cert_from_p12(request.data.get('cert_pwd', ''),
request.data.get('cert_content', None))
if status:
status, result = IosUtils.auto_get_cert_id_by_p12(developer_obj, request.user)
status, _ = IosUtils.auto_get_cert_id_by_p12(developer_obj, request.user)
if status:
resign_app_obj.write_cert()
else:
res.code = 1003
res.msg = '证书未在开发者账户找到,请检查推送证书是否属于该开发者'
res.msg = '证书未在开发者账户找到,请检查证书是否属于该开发者'
else:
res.code = 1002
res.msg = str(result['err_info'])

Loading…
Cancel
Save