实现超级签名方法

super_signature
nineven 5 years ago
parent 8de6e4a87d
commit 789b9a0c58
  1. 55
      fir_client/src/components/FirAppInfossecurity.vue
  2. 19
      fir_client/src/components/FirDownload.vue
  3. 3
      fir_ser/api/admin.py
  4. 18
      fir_ser/api/migrations/0027_apps_issupersign.py
  5. 31
      fir_ser/api/migrations/0028_appudid.py
  6. 38
      fir_ser/api/migrations/0029_auto_20200416_0915.py
  7. 18
      fir_ser/api/migrations/0030_appudid_updated_time.py
  8. 18
      fir_ser/api/migrations/0031_appudid_is_signed.py
  9. 49
      fir_ser/api/migrations/0032_appiosdeveloperinfo_appsupersignusedinfo.py
  10. 23
      fir_ser/api/migrations/0033_auto_20200425_1601.py
  11. 18
      fir_ser/api/migrations/0034_appiosdeveloperinfo_certid.py
  12. 65
      fir_ser/api/models.py
  13. 2
      fir_ser/api/urls.py
  14. 51
      fir_ser/api/utils/TokenManager.py
  15. 2
      fir_ser/api/utils/app/apputils.py
  16. 96
      fir_ser/api/utils/app/iossignapi.py
  17. 127
      fir_ser/api/utils/app/shellcmds.py
  18. 163
      fir_ser/api/utils/app/supersignutils.py
  19. 2
      fir_ser/api/utils/serializer.py
  20. 18
      fir_ser/api/utils/storage/caches.py
  21. 3
      fir_ser/api/views.py
  22. 3
      fir_ser/api/views/apps.py
  23. 64
      fir_ser/api/views/download.py
  24. 7
      fir_ser/fir_ser/settings.py
  25. 157
      fir_ser/supersign/scripts/apple_api.rb

@ -42,6 +42,22 @@
</el-form-item>
<el-form-item v-if="currentapp.type === 1" label-width="160px" label="是否开启超级签名">
<el-tooltip :content="supersign.msg" placement="top">
<el-switch
@change="supersignevent"
v-model="supersign.val"
active-color="#13ce66"
inactive-color="#ff4949"
active-value="on"
inactive-value="off">
</el-switch>
</el-tooltip>
</el-form-item>
</el-form>
</div>
@ -57,10 +73,12 @@
data() {
return {
currentapp: {},
downtip:{},
downtip:{'msg':''},
supersign:{'msg':''},
passwordtip:{'msg':''},
passwordflag:false,
showdownloadflag:false,
showsupersignflag:false,
}
},
methods: {
@ -96,9 +114,20 @@
}
this.showdownloadflag=true;
},
setbuttonsignshow(currentapp){
if(currentapp.issupersign === 1){
this.supersignevent("on");
this.supersign.val='on';
}else {
this.supersignevent("off");
this.supersign.val='off';
}
this.showsupersignflag=true;
},
setbuttondefault(currentapp){
this.setbuttondefaltpass(currentapp);
this.setbuttondefaltshow(currentapp);
this.setbuttonsignshow(currentapp);
},
passwordswitch(state){
this.passwordflag=false;
@ -158,18 +187,35 @@
"isshow": 1,
});
this.currentapp.isshow=1;
}else {
this.downtip.msg='下载页对所有人可见';
}
this.downtip.msg='下载页对所有人可见';
}else {
if(this.showdownloadflag){
this.saveappinfo({
"isshow": 0,
});
this.currentapp.isshow=0;
}else {
}
this.downtip.msg = '下载页不可见'
}
},
supersignevent(newval){
if(newval === "on"){
if(this.showsupersignflag){
this.saveappinfo({
"issupersign": 1,
});
this.currentapp.issupersign=1;
}
this.supersign.msg='已经开启超级签名';
}else {
if(this.showsupersignflag){
this.saveappinfo({
"issupersign": 0,
});
this.currentapp.issupersign=0;
}
this.supersign.msg = '关闭'
}
},
showpasswordevent(newval){
@ -194,6 +240,7 @@
this.currentapp = this.$store.state.currentapp;
this.passwordflag=false;
this.showdownloadflag=false;
this.showsupersignflag=false;
this.setbuttondefault(this.currentapp);
}
},

@ -167,6 +167,7 @@
dchoice:false,
downloadurl:"",
isdownload:false,
udid:"",
}
}, methods: {
download() {
@ -176,9 +177,16 @@
if(res.code === 1000){
if(this.currentappinfo.type === 1){
let download_url = res.data.download_url;
download_url = download_url.replace('http://localhost/download',getplisturl());
this.downloadurl="itms-services://?action=download-manifest&url="+encodeURIComponent(download_url);
if(this.currentappinfo.issupersign && this.udid !== this.$route.query.udid){
if(this.agent !== ''){
let download_url = res.data.download_url;
this.downloadurl = download_url.replace('http://localhost/download',getplisturl());
}
}else {
let download_url = res.data.download_url;
download_url = download_url.replace('http://localhost/download',getplisturl());
this.downloadurl="itms-services://?action=download-manifest&url="+encodeURIComponent(download_url);
}
}else{
if(this.agent !== ''){
this.downloadurl = res.data.download_url;
@ -201,6 +209,7 @@
'short': this.currentappinfo.short,
'release_id': this.mcurrentappinfo.release_id,
'password':this.password,
'udid':this.udid
},
'app_id': this.currentappinfo.app_id
})
@ -218,7 +227,11 @@
if(this.$route.query.release_id){
params["release_id"]=this.$route.query.release_id
}
if(this.$route.query.udid){
params["udid"]=this.$route.query.udid
}
getShortAppinfo(data => {
this.udid = data.udid;
if (data.code === 1000) {
if(!data.data.master_release.release_id){
this.$message({

@ -10,4 +10,7 @@ admin.site.register(AppReleaseInfo)
admin.site.register(Token)
admin.site.register(VerifyName)
admin.site.register(AppStorage)
admin.site.register(AppUDID)
admin.site.register(AppIOSDeveloperInfo)
admin.site.register(APPSuperSignUsedInfo)

@ -0,0 +1,18 @@
# Generated by Django 3.0.3 on 2020-04-15 19:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0026_auto_20200414_1505'),
]
operations = [
migrations.AddField(
model_name='apps',
name='issupersign',
field=models.BigIntegerField(default=False, verbose_name='是否超级签名包'),
),
]

@ -0,0 +1,31 @@
# Generated by Django 3.0.3 on 2020-04-16 08:33
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('api', '0027_apps_issupersign'),
]
operations = [
migrations.CreateModel(
name='AppUDID',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('udid', models.CharField(db_index=True, max_length=64, unique=True, verbose_name='udid唯一标识')),
('product', models.CharField(db_index=True, max_length=64, verbose_name='产品')),
('serial', models.CharField(db_index=True, max_length=64, verbose_name='序列号')),
('version', models.CharField(db_index=True, max_length=64, verbose_name='型号')),
('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('app_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Apps', verbose_name='属于哪个APP')),
],
options={
'verbose_name': '应用详情',
'verbose_name_plural': '应用详情',
'unique_together': {('app_id', 'udid')},
},
),
]

@ -0,0 +1,38 @@
# Generated by Django 3.0.3 on 2020-04-16 09:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0028_appudid'),
]
operations = [
migrations.AddField(
model_name='appudid',
name='iccid',
field=models.CharField(blank=True, max_length=64, null=True, verbose_name='型号'),
),
migrations.AddField(
model_name='appudid',
name='imei',
field=models.CharField(blank=True, max_length=64, null=True, verbose_name='型号'),
),
migrations.AlterField(
model_name='appudid',
name='product',
field=models.CharField(blank=True, max_length=64, null=True, verbose_name='产品'),
),
migrations.AlterField(
model_name='appudid',
name='serial',
field=models.CharField(blank=True, max_length=64, null=True, verbose_name='序列号'),
),
migrations.AlterField(
model_name='appudid',
name='version',
field=models.CharField(blank=True, max_length=64, null=True, verbose_name='型号'),
),
]

@ -0,0 +1,18 @@
# Generated by Django 3.0.3 on 2020-04-16 10:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0029_auto_20200416_0915'),
]
operations = [
migrations.AddField(
model_name='appudid',
name='updated_time',
field=models.DateTimeField(auto_now=True, verbose_name='更新时间'),
),
]

@ -0,0 +1,18 @@
# Generated by Django 3.0.3 on 2020-04-23 17:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0030_appudid_updated_time'),
]
operations = [
migrations.AddField(
model_name='appudid',
name='is_signed',
field=models.BooleanField(default=False, verbose_name='是否完成签名打包'),
),
]

@ -0,0 +1,49 @@
# Generated by Django 3.0.3 on 2020-04-25 15:30
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('api', '0031_appudid_is_signed'),
]
operations = [
migrations.CreateModel(
name='APPSuperSignUsedInfo',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('updated_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('app_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Apps', verbose_name='属于哪个APP')),
('udid', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.AppUDID', verbose_name='所消耗的udid')),
('user_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='用户ID')),
],
options={
'verbose_name': '设备使用统计',
'verbose_name_plural': '设备使用统计',
},
),
migrations.CreateModel(
name='AppIOSDeveloperInfo',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=64, unique=True, verbose_name='存储名字')),
('email', models.EmailField(blank=True, max_length=64, null=True, unique=True, verbose_name='email address')),
('password', models.CharField(max_length=64)),
('is_actived', models.BooleanField(default=False, verbose_name='是否已经激活')),
('usable_number', models.IntegerField(default=100, verbose_name='可使用设备数')),
('use_number', models.IntegerField(default=0, verbose_name='已消耗设备数')),
('created_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('updated_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('user_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='用户ID')),
],
options={
'verbose_name': '苹果开发者账户',
'verbose_name_plural': '苹果开发者账户',
},
),
]

@ -0,0 +1,23 @@
# Generated by Django 3.0.3 on 2020-04-25 16:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0032_appiosdeveloperinfo_appsupersignusedinfo'),
]
operations = [
migrations.AddField(
model_name='appudid',
name='binary_file',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='签名包名称'),
),
migrations.AlterField(
model_name='appiosdeveloperinfo',
name='name',
field=models.CharField(max_length=64, unique=True, verbose_name='开发者标识'),
),
]

@ -0,0 +1,18 @@
# Generated by Django 3.0.3 on 2020-04-25 16:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0033_auto_20200425_1601'),
]
operations = [
migrations.AddField(
model_name='appiosdeveloperinfo',
name='certid',
field=models.CharField(blank=True, max_length=64, null=True, verbose_name='超级签名自动创建证书ID'),
),
]

@ -3,7 +3,7 @@ from django.db import models
# Create your models here.
from django.contrib.contenttypes.models import ContentType
from api.utils.app.randomstrings import make_random_uuid
from api.utils.app.randomstrings import make_random_uuid,make_app_uuid
from django.contrib.auth.models import AbstractUser
######################################## 用户表 ########################################
@ -105,6 +105,7 @@ class Apps(models.Model):
count_hits = models.BigIntegerField(verbose_name="下载次数",default=0)
password = models.CharField(verbose_name="访问密码",default='',help_text='默认 没有密码',max_length=32)
isshow = models.BigIntegerField(verbose_name="下载页可见",default=1)
issupersign = models.BigIntegerField(verbose_name="是否超级签名包",default=False)
description = models.TextField('描述', blank=True, null=True, default=None, )
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
@ -164,3 +165,65 @@ class AppStorage(models.Model):
def __str__(self):
return "%s %s" % (self.user_id.get_username(),self.name)
class AppUDID(models.Model):
app_id = models.ForeignKey(to="Apps", on_delete=models.CASCADE, verbose_name="属于哪个APP")
udid = models.CharField(max_length=64, unique=True,verbose_name="udid唯一标识",db_index=True)
product = models.CharField(max_length=64, verbose_name="产品", blank=True, null=True, )
serial = models.CharField(max_length=64, verbose_name="序列号", blank=True, null=True, )
version = models.CharField(max_length=64, verbose_name="型号", blank=True, null=True, )
imei= models.CharField(max_length=64, verbose_name="型号", blank=True, null=True, )
iccid= models.CharField(max_length=64, verbose_name="型号", blank=True, null=True, )
created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
is_signed = models.BooleanField(verbose_name="是否完成签名打包",default=False)
binary_file = models.CharField(max_length=128,blank=True,verbose_name="签名包名称",null=True)
class Meta:
verbose_name = '应用详情'
verbose_name_plural = "应用详情"
unique_together = ('app_id', 'udid',)
def __str__(self):
return "%s-%s" % (self.app_id.name,self.udid)
class AppIOSDeveloperInfo(models.Model):
user_id = models.ForeignKey(to="UserInfo",verbose_name="用户ID",on_delete=models.CASCADE)
name = models.CharField(max_length=64,verbose_name="开发者标识",unique=True)
email = models.EmailField(
verbose_name='email address',
max_length=64,
unique=True,
blank=True,
null=True
)
password = models.CharField(max_length=64,)
is_actived = models.BooleanField(default=False,verbose_name="是否已经激活")
certid = models.CharField(max_length=64,blank=True,verbose_name="超级签名自动创建证书ID",null=True)
usable_number = models.IntegerField(verbose_name="可使用设备数",default=100)
use_number = models.IntegerField(verbose_name="已消耗设备数",default=0)
created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
verbose_name = '苹果开发者账户'
verbose_name_plural = "苹果开发者账户"
def __str__(self):
return "%s-%s" % (self.name,self.email)
class APPSuperSignUsedInfo(models.Model):
user_id = models.ForeignKey(to="UserInfo",verbose_name="用户ID",on_delete=models.CASCADE)
app_id = models.ForeignKey(to="Apps", on_delete=models.CASCADE, verbose_name="属于哪个APP")
udid = models.ForeignKey(to="AppUDID",on_delete=models.CASCADE, verbose_name="所消耗的udid")
created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
updated_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
verbose_name = '设备使用统计'
verbose_name_plural = "设备使用统计"
def __str__(self):
return "%s-%s-%s" % (self.user_id,self.app_id,self.udid)

@ -23,6 +23,7 @@ from api.views.apps import AppsView,AppInfoView,AppReleaseinfoView
from api.views.download import ShortDownloadView
from api.views.uploads import AppAnalyseView,UploadView
from api.views.storage import StorageView
from api.views.download import IosUDIDView
# router=DefaultRouter()
@ -40,5 +41,6 @@ urlpatterns = [
re_path("^upload$",UploadView.as_view()),
re_path("^userinfo",UserInfoView.as_view()),
re_path("^short/(?P<short>\w+)$", ShortDownloadView.as_view()),
re_path("^udid/(?P<short>\w+)$", IosUDIDView.as_view()),
re_path("^analyse$", AppAnalyseView.as_view()),
]

@ -7,7 +7,6 @@ import uuid
import string
import random
import time
from threading import Thread
from django.core.cache import cache
from fir_ser.settings import CACHE_KEY_TEMPLATE
@ -48,56 +47,6 @@ class DownloadToken(object):
return False
class DownloadTokenLocal(object):
TokenLists = []
flag={'stat':1}
def __init__(self):
self.auto_clean_totken()
def make_token(self,release_id,time_limit=60):
random_str = uuid.uuid1().__str__().split("-")[0:-1]
user_ran_str = uuid.uuid5(uuid.NAMESPACE_DNS, release_id).__str__().split("-")
user_ran_str.extend(random_str)
new_str = "".join(user_ran_str)
self.TokenLists.append({new_str:{
"atime":time.time()+time_limit,
"data":release_id
}})
return new_str
def verify_token(self,token,release_id):
try:
for itoken in self.TokenLists:
if itoken.get(token,None):
if release_id in itoken.get(token,None).get("data",None):
return True
except Exception as e:
print(e)
return False
return False
def auto_clean_totken(self):
if self.flag.get("stat"):
t=Thread(target=self.clean_Token)
t.start()
self.flag["stat"]=0
def clean_Token(self):
while True:
ncount = len(self.TokenLists)
ntime = time.time()
for itokendic in self.TokenLists:
for token, data in itokendic.items():
if int(ntime) - int(data["atime"]) > 0:
self.TokenLists.remove(itokendic)
print("开始清理token,现在token:%s 清理token:%s 剩余token:%s"%(ncount,ncount-len(self.TokenLists),len(self.TokenLists)))
time.sleep(30)
def generateTokenForMedium(medium):
if medium == 'email':
return generateAlphanumericTokenOfLength(32)

@ -133,9 +133,11 @@ def SaveAppInfos(app_file_name,user_obj,appinfo,bundle_id,app_img,short,size):
del_cache_response_by_short(appmobj.short,appmobj.app_id)
appmobj.short = short
appmobj.name = appinfo["labelname"]
appmobj.bundle_id = bundle_id
appmobj.save()
except Exception as e:
print(e)
appmobj.bundle_id = bundle_id
appmobj.name = appinfo["labelname"]
appmobj.save()

@ -0,0 +1,96 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# project: 4月
# author: liuyu
# date: 2020/4/24
from api.utils.app.shellcmds import shell_command,use_user_pass
from fir_ser.settings import SUPER_SIGN_ROOT
import os
from api.utils.app.randomstrings import make_app_uuid
def exec_shell(cmd,remote=False):
if remote:
hostip="10.66.6.66"
port=65534
user="root"
passwd="root"
result = use_user_pass(hostip,port,user,passwd,cmd)
print(result)
return result
else:
result = shell_command(cmd)
print(result)
return result
class AppDeveloperApi(object):
def __init__(self,username,password,certid):
self.username = username
self.password = password
self.certid = certid
script_path=os.path.join(SUPER_SIGN_ROOT,'scripts','apple_api.rb')
self.cmd = "ruby %s '%s' '%s'" %(script_path,self.username,self.password)
def create_cert(self,user_obj):
cert_dir_name = make_app_uuid(user_obj,self.username)
cert_dir_path = os.path.join(SUPER_SIGN_ROOT,cert_dir_name)
if not os.path.isdir(cert_dir_path):
os.makedirs(cert_dir_path)
file_format_path_name = os.path.join(cert_dir_path,cert_dir_name)
self.cmd=self.cmd + " cert add '%s'" %(file_format_path_name)
result = exec_shell(self.cmd)
print(result)
def get_profile(self,bundleId,app_id,device_udid,device_name,provisionName):
self.cmd=self.cmd + " profile add %s %s %s %s %s '%s'" %(bundleId,app_id,device_udid,device_name,self.certid,provisionName)
result = exec_shell(self.cmd)
print(result)
def del_profile(self,bundleId,app_id):
self.cmd=self.cmd + " profile del %s %s" %(bundleId,app_id)
result = exec_shell(self.cmd)
print(result)
def set_device_status(self,status,device_udid):
if status == "enable":
self.cmd=self.cmd + " device enable %s" %(device_udid)
else:
self.cmd=self.cmd + " device disable %s" %(device_udid)
result = exec_shell(self.cmd)
print(result)
def add_device(self,device_udid,device_name):
self.cmd=self.cmd + " device add %s %s" %(device_udid,device_name)
result = exec_shell(self.cmd)
print(result)
def add_app(self,bundleId,app_id):
self.cmd=self.cmd + " app add %s %s" %(bundleId,app_id)
result = exec_shell(self.cmd)
print(result)
def del_app(self,bundleId):
self.cmd=self.cmd + " app del %s " %(bundleId)
result = exec_shell(self.cmd)
print(result)
# appdev = AppDeveloperApi("hehehuyu521@163.com","Hehehuyu123")
# appdev.create_cert()
# appdev.get_profile('com.hehegames.NenJiangMaJiang',"e6f0c1aeee6853af9c21651654a812e7","6dd86c12ab5506a1f7e45b0f58b6b1448f5cdf54","iPad5,1")
# # e6f0c1aeee6853af9c21651654a812e7_dis.mobileprovision
class ResignApp(object):
def __init__(self,my_local_key,app_dev_pem):
self.my_local_key=my_local_key
self.app_dev_pem = app_dev_pem
# 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)
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)
print(result)

@ -0,0 +1,127 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# project: 3月
# author: liuyu
# date: 2020/3/6
import time,re,os
from subprocess import Popen, PIPE
import logging
import paramiko,json
import socket
def default_result():
return {'exit_code': '99', 'return_info': 'Failed to run, function_name is not existed'}
def portisonline(host,port):
result={}
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.settimeout(3)
result["host"] = host
result["port"]=port
result["functionname"]="CHECK_HOST_ONLINE:"
try:
sk.connect((host,port))
result["message"]="Connection success"
result["code"]=0
except Exception:
result["message"]="SSH Port Connection refused"
result["code"]=1
sk.close()
return result
class SSHConnection(object):
def __init__(self, host, port, username, password):
self._host = host
self._port = port
self._username = username
self._password = password
self._transport = None
self._sftp = None
self._client = None
self._connect()
def _connect(self):
transport = paramiko.Transport((self._host, self._port))
transport.connect(username=self._username, password=self._password)
self._transport = transport
#下载
def download(self, remotepath, localpath):
if self._sftp is None:
self._sftp = paramiko.SFTPClient.from_transport(self._transport)
self._sftp.get(remotepath, localpath)
self.close()
#上传
def put(self, localpath, remotepath):
if self._sftp is None:
self._sftp = paramiko.SFTPClient.from_transport(self._transport)
self.exec_command('mkdir -p %s'%(os.path.dirname(remotepath)))
self._sftp.put(localpath, remotepath)
self.close()
#执行命令
def exec_command(self, command):
if self._client is None:
self._client = paramiko.SSHClient()
self._client._transport = self._transport
stdin, stdout, stderr = self._client.exec_command(command)
data = stdout.read()
if len(data) > 0:
return {'status':0,'infos':data}
err = stderr.read()
if len(err) > 0:
return {'status':1,'infos':err}
return {'status': 0, 'infos': data}
def close(self):
if self._transport:
self._transport.close()
if self._client:
self._client.close()
def shell_command(cmdstrs):
cmdresult = re.split(r'\s+', cmdstrs)
result = default_result()
result['return_info'] = ''
shell_start_time = time.time()
child = Popen(cmdresult, shell=True, stdout=PIPE, stderr=PIPE)
out, err = child.communicate()
shell_end_time = time.time()
result['shell_run_time'] = shell_end_time - shell_start_time
out = out.strip(b'\n')
result['return_info'] = out
result['exit_code'] = child.returncode
logging.info(u'shell: %s - %s%s - %s%d' % (cmdstrs, 'return_info: ', out, 'exit_code: ', child.returncode))
return result
def use_user_pass(hostip,port,user,passwd,cmdstrs):
result = default_result()
checkinfo = portisonline(hostip, port)
if checkinfo["code"] != 0:
result['shell_run_time'] = 0
result['return_info'] = json.dumps(checkinfo)
result['exit_code'] = checkinfo["code"]
else:
conn = SSHConnection(hostip, port, user, passwd)
result['return_info'] = ''
shell_start_time = time.time()
out=conn.exec_command(cmdstrs)
shell_end_time = time.time()
result['shell_run_time'] = shell_end_time - shell_start_time
outs =str(out['infos'],'utf-8')
outs = outs.strip('\n')
result['return_info'] = outs
result['exit_code'] = out['status']
# logging.info(u'shell: %s - %s%s - %s%d' % (cmdstrs, 'return_info: ', out, 'exit_code: ', out['status']))
print('host: %s user:%s - shell: %s - %s%s - %s%d' % (hostip,user,cmdstrs, 'return_info: ', out, 'exit_code: ', out['status']))
conn.close()
return result

@ -0,0 +1,163 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# project: 3月
# author: liuyu
# date: 2020/3/6
import uuid, xmltodict,os
from fir_ser.settings import SUPER_SIGN_ROOT,MEDIA_ROOT,SERVER_DOMAIN
from api.utils.app.iossignapi import AppDeveloperApi,ResignApp
from api.models import APPSuperSignUsedInfo,AppUDID,AppIOSDeveloperInfo,AppReleaseInfo
from api.utils.app.randomstrings import make_app_uuid,make_from_user_uuid
from django.db.models import F
def udid_bytes_to_dict(xml_stream):
new_uuid_info = {}
try:
a = xml_stream.find('<plist')
b = xml_stream.find('</plist>')
xml_dict = xmltodict.parse(xml_stream[a:b + 8]) # 解析xml字符串
for i in range(len(xml_dict['plist']['dict']['key'])):
new_uuid_info[xml_dict['plist']['dict']['key'][i].lower()] = xml_dict['plist']['dict']['string'][i]
except Exception as e:
print(e)
return new_uuid_info
def make_udid_mobileconfig(udid_url, PayloadOrganization, PayloadUUID=uuid.uuid1(), PayloadDescription='本文件仅用来获取设备ID',
PayloadDisplayName='查询设备UDID'):
# <!--参考:https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/iPhoneOTAConfiguration/ConfigurationProfileExamples/ConfigurationProfileExamples.html-->
mobileconfig = '''
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<dict>
<key>URL</key>
<string>%s</string>
<key>DeviceAttributes</key>
<array>
<string>SERIAL</string>
<string>UDID</string>
<string>IMEI</string>
<string>ICCID</string>
<string>VERSION</string>
<string>PRODUCT</string>
</array>
</dict>
<key>PayloadOrganization</key>
<string>%s</string>
<key>PayloadDisplayName</key>
<string>%s</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadUUID</key>
<string>%s</string>
<key>PayloadIdentifier</key>
<string>%s.profile-service</string>
<key>PayloadDescription</key>
<string>%s</string>
<key>PayloadType</key>
<string>Profile Service</string>
</dict>
</plist>''' % (udid_url, PayloadOrganization, PayloadDisplayName, PayloadUUID, PayloadOrganization, PayloadDescription)
return mobileconfig
def get_post_udid_url(request, short):
server_domain = get_http_server_doamin(request)
PATH_INFO = request.META.get('PATH_INFO')
PATH_INFO_lists = PATH_INFO.strip('/').split('/')
PATH_INFO_lists[-1] = 'udid'
PATH_INFO_lists.pop(-2)
PATH_INFO_lists.append(short)
PATH_INFO_lists.insert(0, server_domain)
udid_url = "/".join(PATH_INFO_lists)
return udid_url
def get_http_server_doamin(request):
server_domain = SERVER_DOMAIN.get('POST_UDID_DOMAIN', None)
if not server_domain or not server_domain.startswith("http"):
HTTP_HOST = request.META.get('HTTP_HOST')
SERVER_PROTOCOL = request.META.get('SERVER_PROTOCOL')
protocol = 'https'
if SERVER_PROTOCOL == 'HTTP/1.1':
protocol = 'http'
server_domain = "%s://%s" % (protocol, HTTP_HOST)
return server_domain
def get_redirect_server_domain(request):
server_domain = SERVER_DOMAIN.get('REDIRECT_UDID_DOMAIN', None)
if not server_domain or not server_domain.startswith("http"):
HTTP_HOST = request.META.get('HTTP_HOST')
SERVER_PROTOCOL = request.META.get('SERVER_PROTOCOL')
protocol = 'https'
if SERVER_PROTOCOL == 'HTTP/1.1':
protocol = 'http'
server_domain = "%s://%s" % (protocol, HTTP_HOST)
return server_domain
class IosUtils(object):
def __init__(self,udid_info,app_obj):
self.udid_info=udid_info
self.app_obj = app_obj
self.user_obj = app_obj.user_id
self.get_developer_auth()
def get_developer_auth(self):
developer_obj = AppIOSDeveloperInfo.objects.filter(user_id=self.user_obj,is_actived=True,use_number__lte=F("usable_number")).first()
auth = {
"username":developer_obj.email,
"password": developer_obj.password,
"certid":developer_obj.certid
}
self.auth = auth
def download_profile(self):
app_api_obj = AppDeveloperApi(** self.auth)
bundleId = self.app_obj.bundle_id
app_id=self.app_obj.app_id
device_udid= self.udid_info.get('udid')
device_name=self.udid_info.get('product')
app_api_obj.get_profile(bundleId,app_id,device_udid,device_name,self.get_profile_full_path())
def get_profile_full_path(self):
cert_dir_name = make_app_uuid(self.user_obj,self.auth.get("username"))
cert_dir_path = os.path.join(SUPER_SIGN_ROOT,cert_dir_name)
file_format_path_name = os.path.join(cert_dir_path,cert_dir_name,"profile")
if not os.path.isdir(file_format_path_name):
os.makedirs(file_format_path_name)
provisionName = os.path.join(file_format_path_name,self.app_obj.app_id)
return provisionName+ '.mobileprovision'
def resign(self):
self.download_profile()
cert_dir_name = make_app_uuid(self.app_obj.user_id,self.auth.get("username"))
cert_dir_path = os.path.join(SUPER_SIGN_ROOT,cert_dir_name)
if not os.path.isdir(cert_dir_path):
os.makedirs(cert_dir_path)
file_format_path_name = os.path.join(cert_dir_path,cert_dir_name)
my_local_key=file_format_path_name+".key"
app_dev_pem =file_format_path_name+".pem"
ResignAppObj = ResignApp(my_local_key,app_dev_pem)
random_file_name = make_from_user_uuid(self.user_obj)
release_obj = AppReleaseInfo.objects.filter(app_id=self.app_obj,is_master=True).first()
org_file = os.path.join(MEDIA_ROOT, release_obj.release_id + ".ipa")
new_file = os.path.join(MEDIA_ROOT, random_file_name + ".ipa")
ResignAppObj.sign(self.get_profile_full_path(),org_file,new_file)
newdata={
"is_signed":True,
"binary_file":random_file_name + ".ipa"
}
AppUDID.objects.filter(app_id=self.app_obj,udid=self.udid_info.get('udid')).update(**newdata)

@ -82,7 +82,7 @@ class AppsSerializer(serializers.ModelSerializer):
class AppsShortSerializer(serializers.ModelSerializer):
class Meta:
model = models.Apps
fields = ["app_id","name","short","has_combo","isshow","description","need_password",'master_release','type']
fields = ["app_id","name","short","has_combo","isshow","description","need_password",'master_release','type','issupersign']
need_password=serializers.SerializerMethodField()

@ -13,11 +13,15 @@ from api.utils.storage.storage import Storage,LocalStorage
from api.utils.crontab.sync_cache import sync_download_times_by_app_id
from api.utils.crontab import run
def get_download_url_by_cache(app_obj, filename, limit, isdownload=True,key=''):
def get_download_url_by_cache(app_obj, filename, limit, isdownload=True,key='',udid=None):
now = time.time()
if isdownload is None:
local_storage = LocalStorage('localhost', False)
return local_storage.get_download_url(filename, limit, 'plist')
download_url_type = 'plist'
if not udid:
if app_obj.get('issupersign',None):
download_url_type = 'mobileconifg'
return local_storage.get_download_url(filename, limit, download_url_type)
down_key = "_".join([key.lower(), CACHE_KEY_TEMPLATE.get('download_url_key'), filename])
download_val = cache.get(down_key)
if download_val:
@ -33,7 +37,7 @@ def get_app_instance_by_cache(app_id,password, limit):
app_key="_".join([CACHE_KEY_TEMPLATE.get("app_instance_key"),app_id])
app_obj_cache = cache.get(app_key)
if not app_obj_cache:
app_obj_cache = Apps.objects.filter(app_id=app_id).values("pk", 'user_id', 'type','password').first()
app_obj_cache = Apps.objects.filter(app_id=app_id).values("pk", 'user_id', 'type','password','issupersign').first()
cache.set(app_key, app_obj_cache, limit)
app_password=app_obj_cache.get("password")
@ -60,7 +64,7 @@ def set_app_download_by_cache(app_id, limit=900):
set_app_today_download_times(app_id)
return download_times + 1
def del_cache_response_by_short(short,app_id):
def del_cache_response_by_short(short,app_id,udid=''):
cache.delete("_".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):
@ -71,9 +75,9 @@ def del_cache_response_by_short(short,app_id):
key='ShortDownloadView'.lower()
master_release_dict = AppReleaseInfo.objects.filter(app_id__app_id=app_id,is_master=True).values('icon_url','release_id').first()
download_val = CACHE_KEY_TEMPLATE.get('download_url_key')
cache.delete("_".join([key, download_val, os.path.basename(master_release_dict.get("icon_url"))]))
cache.delete("_".join([key,download_val, master_release_dict.get('release_id')]))
cache.delete("_".join([key, CACHE_KEY_TEMPLATE.get("make_token_key"), master_release_dict.get('release_id')]))
cache.delete("_".join([key, download_val, os.path.basename(master_release_dict.get("icon_url")),udid]))
cache.delete("_".join([key,download_val, master_release_dict.get('release_id'),udid]))
cache.delete("_".join([key, CACHE_KEY_TEMPLATE.get("make_token_key"), master_release_dict.get('release_id'),udid]))
def del_cache_by_app_id(app_id,user_obj):

@ -1,3 +0,0 @@
from django.shortcuts import render
# Create your views here.

@ -169,8 +169,11 @@ class AppInfoView(APIView):
apps_obj.name = data.get("name", apps_obj.name)
apps_obj.password = data.get("password", apps_obj.password)
apps_obj.isshow = data.get("isshow", apps_obj.isshow)
if apps_obj.type == 1:
apps_obj.issupersign = data.get("issupersign", apps_obj.issupersign)
apps_obj.save()
except Exception as e:
print(e)
res.code = 1005
res.msg = "短连接已经存在"

@ -10,13 +10,13 @@ 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,udid_bytes_to_dict,get_post_udid_url,get_redirect_server_domain,IosUtils
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
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
import os
from rest_framework_extensions.cache.decorators import cache_response
from api.utils.serializer import AppsShortSerializer
from api.models import Apps,AppReleaseInfo
from api.models import Apps,AppReleaseInfo,AppUDID
from django.http import FileResponse
class DownloadView(APIView):
'''
@ -56,16 +56,24 @@ class DownloadView(APIView):
name = release_obj.app_id.name
ios_plist_bytes = make_resigned(storage.get_download_url(filename),storage.get_download_url(release_obj.icon_url),bundle_id,app_version,name)
response = FileResponse(ios_plist_bytes)
response['content_type'] = "application/x-plist"
response['Content-Type'] = "application/x-plist"
response['Content-Disposition'] = 'attachment; filename=' + make_random_uuid()
return response
elif ftype == 'mobileconifg':
release_obj = AppReleaseInfo.objects.filter(release_id=filename.split('.')[0]).first()
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)
response = FileResponse(ios_udid_mobileconfig)
response['Content-Type'] = "application/x-apple-aspen-config"
response['Content-Disposition'] = 'attachment; filename=' + make_random_uuid() +'.mobileconfig'
return response
res.code=1004
res.msg="token校验失败"
return Response(res.dict)
class ShortDownloadView(APIView):
'''
根据下载短链接获取应用信息
@ -75,7 +83,10 @@ class ShortDownloadView(APIView):
def get(self,request,short):
res = BaseResponse()
release_id = request.query_params.get("release_id", None)
udid = request.query_params.get("udid", None)
app_obj = Apps.objects.filter(short=short).first()
if udid:
del_cache_response_by_short(short,app_obj.app_id,udid=udid)
if not app_obj:
res.code=1003
res.msg="该应用不存在"
@ -88,13 +99,15 @@ class ShortDownloadView(APIView):
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
return Response(res.dict)
#key的设置
def calculate_cache_key(self, view_instance, view_method,
request, args, kwargs):
release_id = request.query_params.get("release_id", '')
return "_".join([settings.CACHE_KEY_TEMPLATE.get("download_short_key"), kwargs.get("short", ''),release_id])
udid = request.query_params.get("udid", '')
return "_".join([settings.CACHE_KEY_TEMPLATE.get("download_short_key"), kwargs.get("short", ''),release_id,udid])
class InstallView(APIView):
'''
@ -108,6 +121,7 @@ class InstallView(APIView):
release_id = request.query_params.get("release_id",None)
isdownload = request.query_params.get("isdownload",None)
password = request.query_params.get("password",None)
udid = request.query_params.get("udid",None)
if not downtoken or not short or not release_id:
res.code=1004
@ -125,9 +139,9 @@ class InstallView(APIView):
else:
apptype = '.ipa'
if isdownload :
download_url = get_download_url_by_cache(app_obj,release_id + apptype, 600)
download_url = get_download_url_by_cache(app_obj,release_id + apptype, 600,udid=udid)
else:
download_url = get_download_url_by_cache(app_obj,release_id + apptype,600,isdownload)
download_url = get_download_url_by_cache(app_obj,release_id + apptype,600,isdownload,udid=udid)
res.data={"download_url":download_url}
return Response(res.dict)
@ -140,3 +154,35 @@ class InstallView(APIView):
res.msg = "该应用不存在"
return Response(res.dict)
from django.views import View
from django.http import HttpResponsePermanentRedirect,Http404
class IosUDIDView(View):
def post(self,request,short):
stream_f = str(request.body)
format_udid_info = udid_bytes_to_dict(stream_f)
print(format_udid_info,short)
try:
app_info=Apps.objects.filter(short=short).values("pk",'issupersign').first()
if app_info :
if app_info.get('issupersign'):
AppUDID.objects.update_or_create(app_id_id= app_info.get('pk'),**format_udid_info,defaults={'udid':format_udid_info.get('udid')})
ios_obj = IosUtils(format_udid_info,app_info)
ios_obj.resign()
else:
return Http404
else:
return Http404
except Exception as e:
print(e)
server_domain = get_redirect_server_domain(request)
return HttpResponsePermanentRedirect("%s/%s?udid=%s"%(server_domain,short,format_udid_info.get("udid")))

@ -145,6 +145,8 @@ STATICFILES_DIRS=[
# Media配置
MEDIA_URL = "files/"
MEDIA_ROOT = os.path.join(BASE_DIR, "files")
#supersign配置
SUPER_SIGN_ROOT = os.path.join(BASE_DIR, "supersign")
AUTH_USER_MODEL="api.UserInfo"
@ -247,5 +249,10 @@ SYNC_CACHE_TO_DATABASE={
'download_times':10 #下载次数同步时间
}
SERVER_DOMAIN={
'POST_UDID_DOMAIN':'http://192.168.8.236:8000', #超级签名调试
'REDIRECT_UDID_DOMAIN': 'http://192.168.8.236:8080', # 超级签名调试
}
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"

@ -0,0 +1,157 @@
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
require "spaceship"
class DevelopPortalHandle
def login(username,password)
Spaceship::Portal.login(username,password)
end
def createCert(file_format_path_name)
csr, pkey = Spaceship::Portal.certificate.create_certificate_signing_request
cert=Spaceship::Portal.certificate.production.create!(csr: csr)
File.write(file_format_path_name+".key",pkey)
File.write(file_format_path_name+".cer",cert.download_raw)
File.write(file_format_path_name+".pem",cert.download)
File.write(file_format_path_name+".info",cert)
end
def createApp(appid,appname)
app = Spaceship::Portal.app.find(appid)
if !app then
app = Spaceship::Portal.app.create!(bundle_id: appid, name: appname)
app.update_service(Spaceship::Portal.app_service.push_notification.on)
app.update_service(Spaceship::Portal.app_service.vpn_configuration.on)
end
end
def deleteApp(appid)
app = Spaceship::Portal.app.find(appid)
if app then
app.delete!
end
end
#appstore or inHouse
def createDistributionProvision(provisioningClass,appid,provisionName,certid)
cert = Spaceship::Portal.certificate.Production.find(id=certid)
if !cert then
cert = Spaceship::Portal.certificate.production.all.last
end
profile = provisioningClass.create!(bundle_id: appid,certificate:cert,name:provisionName + '_dis')
return profile
end
#appstore or inHouse
def downloadDistributionProvision(provisioningClass,appid,provisionName,certid)
#查找有没有provision文件
filtered_profiles = provisioningClass.find_by_bundle_id(bundle_id: appid)
profile = nil
if 0 < filtered_profiles.length then
profile = filtered_profiles[0]
elsif 0 == filtered_profiles.length then
profile = createDistributionProvision(provisioningClass,appid,provisionName,certid)
end
#没有找到就创建,找到就下载
File.write(provisionName, profile.download)
return provisionFileName
end
def delete_profile(provisioningClass,appid)
filtered_profiles = provisioningClass.find_by_bundle_id(bundle_id: appid)
profile = nil
if 0 < filtered_profiles.length then
profile = filtered_profiles[0]
profile.delete!
end
end
def addDevice(device_name,device_udid)
device = Spaceship::Portal.device.find_by_udid(device_udid, include_disabled: false)
if !device then
Spaceship::Portal.device.create!(name: device_name, udid: device_udid)
end
end
def enableDevice(device_udid)
device=Spaceship::Portal.device.find_by_udid(device_udid, include_disabled: true)
if device then
device.enable!
end
end
def disableDevice(device_udid)
device = Spaceship::Portal.device.find_by_udid(device_udid)
if device then
device.disable!
end
end
end
handle = DevelopPortalHandle.new()
handle.login(ARGV[0],ARGV[1])
function = ARGV[2]
case function
when "device"
action = ARGV[3]
device_udid = ARGV[4]
device_name = ARGV[5]
case action
when "add"
device_name = device_udid+','+device_udid
handle.addDevice(device_name,device_udid)
when "enable"
handle.enableDevice(device_udid)
when "disable"
handle.disableDevice(device_udid)
end
when "app"
action = ARGV[3]
app_id = ARGV[4]
app_name = ARGV[5]
appid = app_id+app_name
case action
when "add"
handle.createApp(appid,app_name)
when "del"
handle.deleteApp(appid)
handle.delete_profile(Spaceship::Portal.provisioning_profile.ad_hoc,appid)
end
when "profile"
action = ARGV[3]
app_id = ARGV[4]
app_name = ARGV[5]
appid = app_id+app_name
case action
when "add"
device_udid = ARGV[6]
device_name = ARGV[7]
certid = ARGV[8]
provisionName = ARGV[9]
handle.createApp(appid,app_name)
handle.addDevice(device_name,device_udid)
provisionPath = handle.downloadDistributionProvision(Spaceship::Portal.provisioning_profile.ad_hoc,appid,provisionName,certid)
when "del"
handle.delete_profile(Spaceship::Portal.provisioning_profile.ad_hoc,appid)
end
when "cert"
action = ARGV[3]
file_format_path_name = ARGV[4]
case action
when "add"
handle.createCert(file_format_path_name)
end
else
puts "error"
end
Loading…
Cancel
Save