diff --git a/fir_ser/api/utils/app/iossignapi.py b/fir_ser/api/utils/app/iossignapi.py index f8d6329..3a61e62 100644 --- a/fir_ser/api/utils/app/iossignapi.py +++ b/fir_ser/api/utils/app/iossignapi.py @@ -111,6 +111,13 @@ class ResignApp(object): # script_path=os.path.join(SUPER_SIGN_ROOT,'scripts','apple_api.rb') self.cmd = "isign -c '%s' -k '%s' " % (self.app_dev_pem, self.my_local_key) + @staticmethod + def sign_mobileconfig(mobilconfig_path, sign_mobilconfig_path, ssl_pem_path, ssl_key_path): + cmd = "openssl smime -sign -in %s -out %s -signer %s " \ + "-inkey %s -certfile %s -outform der -nodetach " % ( + mobilconfig_path, sign_mobilconfig_path, ssl_pem_path, ssl_key_path, ssl_pem_path) + return exec_shell(cmd) + def sign(self, new_profile, org_ipa, new_ipa): self.cmd = self.cmd + " -p '%s' -o '%s' '%s'" % (new_profile, new_ipa, org_ipa) result = exec_shell(self.cmd) diff --git a/fir_ser/api/utils/app/supersignutils.py b/fir_ser/api/utils/app/supersignutils.py index 0c70c05..2f74bd9 100644 --- a/fir_ser/api/utils/app/supersignutils.py +++ b/fir_ser/api/utils/app/supersignutils.py @@ -5,7 +5,7 @@ # date: 2020/3/6 import uuid, xmltodict, os, re, logging -from fir_ser.settings import SUPER_SIGN_ROOT, MEDIA_ROOT, SERVER_DOMAIN +from fir_ser.settings import SUPER_SIGN_ROOT, MEDIA_ROOT, SERVER_DOMAIN, MOBILECONFIG_SIGN_SSL from api.utils.app.iossignapi import AppDeveloperApi, ResignApp from api.models import APPSuperSignUsedInfo, AppUDID, AppIOSDeveloperInfo, AppReleaseInfo, Apps, APPToDeveloper, \ UDIDsyncDeveloper @@ -32,8 +32,45 @@ def udid_bytes_to_dict(xml_stream): return new_uuid_info -def make_udid_mobileconfig(udid_url, PayloadOrganization, PayloadUUID=uuid.uuid1(), PayloadDescription='本文件仅用来获取设备ID', - PayloadDisplayName='查询设备UDID'): +def make_sign_udid_mobileconfig(udid_url, PayloadOrganization, appname): + if MOBILECONFIG_SIGN_SSL.get("open"): + ssl_key_path = MOBILECONFIG_SIGN_SSL.get("ssl_key_path", None) + ssl_pem_path = MOBILECONFIG_SIGN_SSL.get("ssl_pem_path", None) + + if ssl_key_path and ssl_pem_path and os.path.isfile(ssl_key_path) and os.path.isfile(ssl_pem_path): + mobileconfig_tmp_dir = os.path.join(SUPER_SIGN_ROOT, 'tmp', 'mobileconfig') + if not os.path.exists(mobileconfig_tmp_dir): + os.makedirs(mobileconfig_tmp_dir) + + mobileconfig_filename = PayloadOrganization + str(uuid.uuid1()) + mobilconfig_path = os.path.join(mobileconfig_tmp_dir, mobileconfig_filename) + + sign_mobilconfig_path = os.path.join(mobileconfig_tmp_dir, 'sign_' + mobileconfig_filename) + with open(mobilconfig_path, "w") as f: + f.write(make_udid_mobileconfig(udid_url, PayloadOrganization, appname)) + + status, result = ResignApp.sign_mobileconfig(mobilconfig_path, sign_mobilconfig_path, ssl_pem_path, + ssl_key_path) + if status: + mobileconfig_body = open(sign_mobilconfig_path, 'rb') + else: + logger.error( + "%s %s sign_mobileconfig failed ERROR:%s" % (PayloadOrganization, appname, result.get("err_info"))) + return make_udid_mobileconfig(udid_url, PayloadOrganization, appname) + + return mobileconfig_body + + else: + logger.error("sign_mobileconfig %s or %s is not exists" % (ssl_key_path, ssl_pem_path)) + return make_udid_mobileconfig(udid_url, PayloadOrganization, appname) + + else: + return make_udid_mobileconfig(udid_url, PayloadOrganization, appname) + + +def make_udid_mobileconfig(udid_url, PayloadOrganization, appname, PayloadUUID=uuid.uuid1(), + PayloadDescription='该文件仅用来获取设备ID,帮助用户安装授权', + PayloadDisplayName='设备安装授权'): # mobileconfig = ''' @@ -68,7 +105,8 @@ def make_udid_mobileconfig(udid_url, PayloadOrganization, PayloadUUID=uuid.uuid1 PayloadType Profile Service -''' % (udid_url, PayloadOrganization, PayloadDisplayName, PayloadUUID, PayloadOrganization, PayloadDescription) +''' % (udid_url, PayloadOrganization, appname + " -- " + PayloadDisplayName, PayloadUUID, PayloadOrganization, + PayloadDescription) return mobileconfig diff --git a/fir_ser/api/utils/crontab/run.py b/fir_ser/api/utils/crontab/run.py index 3bdd623..89b8ee8 100644 --- a/fir_ser/api/utils/crontab/run.py +++ b/fir_ser/api/utils/crontab/run.py @@ -7,7 +7,8 @@ from apscheduler.schedulers.background import BackgroundScheduler from django_apscheduler.jobstores import DjangoJobStore, register_events, register_job from fir_ser.settings import SYNC_CACHE_TO_DATABASE -from api.utils.crontab.sync_cache import sync_download_times, auto_clean_upload_tmp_file, auto_delete_job_log +from api.utils.crontab.sync_cache import sync_download_times, auto_clean_upload_tmp_file, auto_delete_job_log, \ + auto_delete_tmp_file import logging logger = logging.getLogger(__file__) @@ -40,11 +41,15 @@ try: # @register_job(scheduler, 'cron', day_of_week='mon-fri', hour='9', minute='30', second='10',id='task_time') @register_job(scheduler, "interval", seconds=SYNC_CACHE_TO_DATABASE.get("auto_clean_tmp_file_times")) def auto_clean_upload_tmp_file_job(): - # 这里写你要执行的任务 auto_clean_upload_tmp_file() auto_delete_job_log() + @register_job(scheduler, "interval", seconds=SYNC_CACHE_TO_DATABASE.get("auto_clean_local_tmp_file_times")) + def auto_delete_tmp_file_job(): + auto_delete_tmp_file() + + register_events(scheduler) scheduler.start() except Exception as e: diff --git a/fir_ser/api/utils/crontab/sync_cache.py b/fir_ser/api/utils/crontab/sync_cache.py index 8b20e2c..e93a505 100644 --- a/fir_ser/api/utils/crontab/sync_cache.py +++ b/fir_ser/api/utils/crontab/sync_cache.py @@ -7,8 +7,8 @@ from api.models import Apps, UserInfo from api.utils.storage.storage import Storage from django.core.cache import cache -from fir_ser.settings import CACHE_KEY_TEMPLATE, SYNC_CACHE_TO_DATABASE -import time +from fir_ser.settings import CACHE_KEY_TEMPLATE, SYNC_CACHE_TO_DATABASE, SUPER_SIGN_ROOT +import time, os from django_apscheduler.models import DjangoJobExecution import logging @@ -65,3 +65,17 @@ def auto_delete_job_log(): count = DjangoJobExecution.objects.count() if count > need_count: DjangoJobExecution.objects.filter(id__lte=max_id - need_count).delete() + + +def auto_delete_tmp_file(): + mobileconfig_tmp_dir = os.path.join(SUPER_SIGN_ROOT, 'tmp', 'mobileconfig') + for root, dirs, files in os.walk(mobileconfig_tmp_dir, topdown=False): + now_time = time.time() + for name in files: + file_path = os.path.join(root, name) + st_mtime = os.stat(file_path).st_mtime + if now_time - st_mtime > SYNC_CACHE_TO_DATABASE.get('clean_local_tmp_file_from_mtime', 30 * 60): + try: + os.remove(file_path) + except Exception as e: + logger.error("auto_delete_tmp_file %s Failed . Exception %s" % (file_path, e)) diff --git a/fir_ser/api/views/download.py b/fir_ser/api/views/download.py index 3dc49bd..c845e6f 100644 --- a/fir_ser/api/views/download.py +++ b/fir_ser/api/views/download.py @@ -10,7 +10,7 @@ from fir_ser import settings from api.utils.TokenManager import DownloadToken from api.utils.app.randomstrings import make_random_uuid from api.utils.app.apputils import make_resigned -from api.utils.app.supersignutils import make_udid_mobileconfig, get_post_udid_url, get_redirect_server_domain +from api.utils.app.supersignutils import make_sign_udid_mobileconfig, get_post_udid_url, get_redirect_server_domain from api.utils.storage.storage import Storage from api.utils.storage.caches import get_app_instance_by_cache, get_download_url_by_cache, set_app_download_by_cache, \ del_cache_response_by_short @@ -69,7 +69,7 @@ class DownloadView(APIView): if release_obj: bundle_id = release_obj.app_id.bundle_id udid_url = get_post_udid_url(request, release_obj.app_id.short) - ios_udid_mobileconfig = make_udid_mobileconfig(udid_url, bundle_id) + ios_udid_mobileconfig = make_sign_udid_mobileconfig(udid_url, bundle_id, release_obj.app_id.name) response = FileResponse(ios_udid_mobileconfig) response['Content-Type'] = "application/x-apple-aspen-config" response['Content-Disposition'] = 'attachment; filename=' + make_random_uuid() + '.mobileconfig' diff --git a/fir_ser/fir_ser/settings.py b/fir_ser/fir_ser/settings.py index 57e190f..bda612c 100644 --- a/fir_ser/fir_ser/settings.py +++ b/fir_ser/fir_ser/settings.py @@ -230,8 +230,10 @@ FILE_UPLOAD_TMP_KEY = ".tmp" SYNC_CACHE_TO_DATABASE = { 'download_times': 10, # 下载次数同步时间 'auto_clean_tmp_file_times': 60 * 30, # 定时清理上传失误生成的临时文件 - 'auto_clean_apscheduler_log': 100000, # 定时清理定时任务执行的日志,该参数为日志保留的数量 + 'auto_clean_local_tmp_file_times': 60 * 30, # 定时清理临时文件,现在包含超级签名描述临时文件 'try_login_times': 5, # 当天登录失败次数,超过该失败次数,锁定24小时 + 'auto_clean_apscheduler_log': 100000, # 定时清理定时任务执行的日志,该日志存在数据库中,该参数为日志保留的数量 + 'clean_local_tmp_file_from_mtime': 60 * 60, # 清理最后一次修改时间超过限制时间的临时文件,单位秒 } SERVER_DOMAIN = { @@ -244,6 +246,13 @@ SERVER_DOMAIN = { 'FILE_UPLOAD_DOMAIN': 'https://fly.harmonygames.cn', # 本地文件上传域名,使用本地存储必须配置 } +MOBILECONFIG_SIGN_SSL = { + # 描述文件是否签名,默认是关闭状态;如果开启,并且ssl_key_path 和 ssl_pem_path 正常,则使用填写的ssl进行签名,否则默认不签名 + 'open': False, + 'ssl_key_path': '/data/cert/fly.harmonygames.cn.key', + 'ssl_pem_path': '/data/cert/fly.harmonygames.cn.pem' +} + SESSION_ENGINE = "django.contrib.sessions.backends.cache" SESSION_CACHE_ALIAS = "default"