Coverage for integrations / agent_engine / build_distribution.py: 43.3%

150 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-05-12 04:49 +0000

1""" 

2Linux Build Distribution — Licensed commercial builds gated by payment. 

3 

4Purchase → signed license → time-limited download URL. 

5All compute should fall under one basket — treading carefully in a cautious market. 

6 

7Service Pattern: static methods, db: Session, db.flush() not db.commit(). 

8Blueprint Pattern: Blueprint('build_distribution', __name__). 

9""" 

10import hashlib 

11import hmac 

12import logging 

13import secrets 

14import time 

15from datetime import datetime, timedelta 

16from typing import Dict, List, Optional 

17 

18from flask import Blueprint, g, jsonify, request 

19from sqlalchemy.orm import Session 

20 

21logger = logging.getLogger('hevolve_social') 

22 

23# Build type → (max_downloads, validity_days) 

24BUILD_CONFIG = { 

25 'community': (3, 365), # 3 downloads, 1 year 

26 'pro': (10, 730), # 10 downloads, 2 years 

27 'enterprise': (100, 36500), # 100 downloads, ~perpetual (100 years) 

28} 

29 

30# HMAC secret for signed download URLs (rotated at boot) 

31_DOWNLOAD_SECRET = secrets.token_bytes(32) 

32 

33 

34class BuildDistributionService: 

35 """Static service for licensed Linux build distribution.""" 

36 

37 @staticmethod 

38 def create_build_license(db: Session, user_id: str, 

39 build_type: str = 'community', 

40 platform: str = 'linux_x64', 

41 payment_reference: str = None) -> Dict: 

42 """Create a signed build license after payment verification.""" 

43 from integrations.social.models import BuildLicense 

44 

45 if build_type not in BUILD_CONFIG: 

46 return {'error': f'Invalid build_type: {build_type}. Valid: {list(BUILD_CONFIG.keys())}'} 

47 

48 if platform not in ('linux_x64', 'linux_arm64'): 

49 return {'error': f'Invalid platform: {platform}. Valid: linux_x64, linux_arm64'} 

50 

51 max_downloads, validity_days = BUILD_CONFIG[build_type] 

52 license_key = secrets.token_urlsafe(32) 

53 

54 # Sign with node key if available 

55 signed_by = None 

56 sig_hex = None 

57 try: 

58 from security.node_integrity import get_public_key_hex, sign_message 

59 signed_by = get_public_key_hex() 

60 sig_hex = sign_message(license_key.encode('utf-8')).hex() 

61 except Exception: 

62 pass 

63 

64 bl = BuildLicense( 

65 user_id=user_id, 

66 license_key=license_key, 

67 build_type=build_type, 

68 platform=platform, 

69 payment_reference=payment_reference, 

70 max_downloads=max_downloads, 

71 signed_by=signed_by, 

72 signature_hex=sig_hex, 

73 expires_at=datetime.utcnow() + timedelta(days=validity_days), 

74 ) 

75 db.add(bl) 

76 db.flush() 

77 return bl.to_dict() 

78 

79 @staticmethod 

80 def verify_build_license(db: Session, license_key: str) -> Dict: 

81 """Verify a license key. Returns {valid: bool, license: dict, reason: str}.""" 

82 from integrations.social.models import BuildLicense 

83 

84 bl = db.query(BuildLicense).filter_by(license_key=license_key).first() 

85 if not bl: 

86 return {'valid': False, 'license': None, 'reason': 'License not found'} 

87 if not bl.is_active: 

88 return {'valid': False, 'license': bl.to_dict(), 'reason': 'License deactivated'} 

89 if bl.expires_at and bl.expires_at < datetime.utcnow(): 

90 return {'valid': False, 'license': bl.to_dict(), 'reason': 'License expired'} 

91 if bl.download_count >= bl.max_downloads: 

92 return {'valid': False, 'license': bl.to_dict(), 'reason': 'Download limit reached'} 

93 

94 return {'valid': True, 'license': bl.to_dict(), 'reason': 'ok'} 

95 

96 @staticmethod 

97 def record_download(db: Session, license_id: str) -> Dict: 

98 """Record a download. Returns error if limit reached.""" 

99 from integrations.social.models import BuildLicense 

100 

101 bl = db.query(BuildLicense).filter_by(id=license_id).first() 

102 if not bl: 

103 return {'error': 'License not found'} 

104 if bl.download_count >= bl.max_downloads: 

105 return {'error': 'Download limit reached'} 

106 if not bl.is_active: 

107 return {'error': 'License deactivated'} 

108 

109 bl.download_count = (bl.download_count or 0) + 1 

110 db.flush() 

111 return bl.to_dict() 

112 

113 @staticmethod 

114 def get_download_url(db: Session, license_id: str) -> Dict: 

115 """Generate a time-limited signed download URL (1 hour).""" 

116 from integrations.social.models import BuildLicense 

117 

118 bl = db.query(BuildLicense).filter_by(id=license_id).first() 

119 if not bl: 

120 return {'error': 'License not found'} 

121 

122 # Verify validity 

123 if not bl.is_active: 

124 return {'error': 'License deactivated'} 

125 if bl.expires_at and bl.expires_at < datetime.utcnow(): 

126 return {'error': 'License expired'} 

127 if bl.download_count >= bl.max_downloads: 

128 return {'error': 'Download limit reached'} 

129 

130 # Increment download count 

131 bl.download_count = (bl.download_count or 0) + 1 

132 db.flush() 

133 

134 # Generate signed URL (HMAC-based, 1 hour validity) 

135 expires = int(time.time()) + 3600 

136 payload = f"{license_id}:{bl.platform}:{bl.build_type}:{expires}" 

137 signature = hmac.new( 

138 _DOWNLOAD_SECRET, payload.encode(), hashlib.sha256 

139 ).hexdigest()[:32] 

140 

141 url = (f"/api/v1/builds/files/{bl.platform}/{bl.build_type}" 

142 f"?license={license_id}&expires={expires}&sig={signature}") 

143 

144 return { 

145 'url': url, 

146 'expires_at': datetime.utcfromtimestamp(expires).isoformat(), 

147 'platform': bl.platform, 

148 'build_type': bl.build_type, 

149 'downloads_remaining': bl.max_downloads - bl.download_count, 

150 } 

151 

152 @staticmethod 

153 def list_licenses(db: Session, user_id: str) -> List[Dict]: 

154 """List all licenses for a user.""" 

155 from integrations.social.models import BuildLicense 

156 licenses = db.query(BuildLicense).filter_by( 

157 user_id=user_id).order_by(BuildLicense.created_at.desc()).all() 

158 return [bl.to_dict() for bl in licenses] 

159 

160 

161# ═══════════════════════════════════════════════════════════════ 

162# Blueprint 

163# ═══════════════════════════════════════════════════════════════ 

164 

165build_distribution_bp = Blueprint('build_distribution', __name__) 

166 

167 

168@build_distribution_bp.route('/api/v1/builds/purchase', methods=['POST']) 

169def purchase_build(): 

170 """Purchase a build license (requires JWT auth).""" 

171 auth_header = request.headers.get('Authorization', '') 

172 if not auth_header.startswith('Bearer '): 

173 return jsonify({'success': False, 'error': 'Authorization required'}), 401 

174 

175 from integrations.social.auth import _get_user_from_token 

176 token = auth_header[7:] 

177 user, db = _get_user_from_token(token) 

178 if not user: 

179 if db: 

180 db.close() 

181 return jsonify({'success': False, 'error': 'Invalid token'}), 401 

182 

183 try: 

184 data = request.get_json() or {} 

185 result = BuildDistributionService.create_build_license( 

186 db, str(user.id), 

187 build_type=data.get('build_type', 'community'), 

188 platform=data.get('platform', 'linux_x64'), 

189 payment_reference=data.get('payment_reference'), 

190 ) 

191 if 'error' in result: 

192 return jsonify({'success': False, 'error': result['error']}), 400 

193 db.commit() 

194 return jsonify({'success': True, 'license': result}), 201 

195 finally: 

196 db.close() 

197 

198 

199@build_distribution_bp.route('/api/v1/builds/download/<license_id>', methods=['GET']) 

200def download_build(license_id): 

201 """Get signed download URL for a license (requires JWT auth).""" 

202 auth_header = request.headers.get('Authorization', '') 

203 if not auth_header.startswith('Bearer '): 

204 return jsonify({'success': False, 'error': 'Authorization required'}), 401 

205 

206 from integrations.social.auth import _get_user_from_token 

207 token = auth_header[7:] 

208 user, db = _get_user_from_token(token) 

209 if not user: 

210 if db: 

211 db.close() 

212 return jsonify({'success': False, 'error': 'Invalid token'}), 401 

213 

214 try: 

215 result = BuildDistributionService.get_download_url(db, license_id) 

216 if 'error' in result: 

217 return jsonify({'success': False, 'error': result['error']}), 400 

218 db.commit() 

219 return jsonify({'success': True, 'download': result}) 

220 finally: 

221 db.close() 

222 

223 

224@build_distribution_bp.route('/api/v1/builds/licenses', methods=['GET']) 

225def list_build_licenses(): 

226 """List user's build licenses (requires JWT auth).""" 

227 auth_header = request.headers.get('Authorization', '') 

228 if not auth_header.startswith('Bearer '): 

229 return jsonify({'success': False, 'error': 'Authorization required'}), 401 

230 

231 from integrations.social.auth import _get_user_from_token 

232 token = auth_header[7:] 

233 user, db = _get_user_from_token(token) 

234 if not user: 

235 if db: 

236 db.close() 

237 return jsonify({'success': False, 'error': 'Invalid token'}), 401 

238 

239 try: 

240 licenses = BuildDistributionService.list_licenses(db, str(user.id)) 

241 return jsonify({'success': True, 'licenses': licenses}) 

242 finally: 

243 db.close() 

244 

245 

246@build_distribution_bp.route('/api/v1/builds/verify', methods=['POST']) 

247def verify_license(): 

248 """Public license verification endpoint.""" 

249 from integrations.social.models import get_db 

250 

251 data = request.get_json() or {} 

252 license_key = data.get('license_key', '') 

253 if not license_key: 

254 return jsonify({'success': False, 'error': 'license_key required'}), 400 

255 

256 db = get_db() 

257 try: 

258 result = BuildDistributionService.verify_build_license(db, license_key) 

259 return jsonify({'success': True, 'verification': result}) 

260 finally: 

261 db.close()