Forking what is left of ZeroNet and hopefully adding an AT Proto Frontend/Proxy
1import logging
2import json
3import time
4import binascii
5
6import gevent
7
8import util
9from Crypt import CryptBitcoin
10from Plugin import PluginManager
11from Config import config
12from util import helper
13from Debug import Debug
14
15
16@PluginManager.acceptPlugins
17class User(object):
18 def __init__(self, master_address=None, master_seed=None, data={}):
19 if master_seed:
20 self.master_seed = master_seed
21 self.master_address = CryptBitcoin.privatekeyToAddress(self.master_seed)
22 elif master_address:
23 self.master_address = master_address
24 self.master_seed = data.get("master_seed")
25 else:
26 self.master_seed = CryptBitcoin.newSeed()
27 self.master_address = CryptBitcoin.privatekeyToAddress(self.master_seed)
28 self.sites = data.get("sites", {})
29 self.certs = data.get("certs", {})
30 self.settings = data.get("settings", {})
31 self.delayed_save_thread = None
32
33 self.log = logging.getLogger("User:%s" % self.master_address)
34
35 # Save to data/users.json
36 @util.Noparallel(queue=True, ignore_class=True)
37 def save(self):
38 s = time.time()
39 users = json.load(open("%s/users.json" % config.data_dir))
40 if self.master_address not in users:
41 users[self.master_address] = {} # Create if not exist
42 user_data = users[self.master_address]
43 if self.master_seed:
44 user_data["master_seed"] = self.master_seed
45 user_data["sites"] = self.sites
46 user_data["certs"] = self.certs
47 user_data["settings"] = self.settings
48 helper.atomicWrite("%s/users.json" % config.data_dir, helper.jsonDumps(users).encode("utf8"))
49 self.log.debug("Saved in %.3fs" % (time.time() - s))
50 self.delayed_save_thread = None
51
52 def saveDelayed(self):
53 if not self.delayed_save_thread:
54 self.delayed_save_thread = gevent.spawn_later(5, self.save)
55
56 def getAddressAuthIndex(self, address):
57 return int(binascii.hexlify(address.encode()), 16)
58
59 @util.Noparallel()
60 def generateAuthAddress(self, address):
61 s = time.time()
62 address_id = self.getAddressAuthIndex(address) # Convert site address to int
63 auth_privatekey = CryptBitcoin.hdPrivatekey(self.master_seed, address_id)
64 self.sites[address] = {
65 "auth_address": CryptBitcoin.privatekeyToAddress(auth_privatekey),
66 "auth_privatekey": auth_privatekey
67 }
68 self.saveDelayed()
69 self.log.debug("Added new site: %s in %.3fs" % (address, time.time() - s))
70 return self.sites[address]
71
72 # Get user site data
73 # Return: {"auth_address": "xxx", "auth_privatekey": "xxx"}
74 def getSiteData(self, address, create=True):
75 if address not in self.sites: # Generate new BIP32 child key based on site address
76 if not create:
77 return {"auth_address": None, "auth_privatekey": None} # Dont create user yet
78 self.generateAuthAddress(address)
79 return self.sites[address]
80
81 def deleteSiteData(self, address):
82 if address in self.sites:
83 del(self.sites[address])
84 self.saveDelayed()
85 self.log.debug("Deleted site: %s" % address)
86
87 def setSiteSettings(self, address, settings):
88 site_data = self.getSiteData(address)
89 site_data["settings"] = settings
90 self.saveDelayed()
91 return site_data
92
93 # Get data for a new, unique site
94 # Return: [site_address, bip32_index, {"auth_address": "xxx", "auth_privatekey": "xxx", "privatekey": "xxx"}]
95 def getNewSiteData(self):
96 import random
97 bip32_index = random.randrange(2 ** 256) % 100000000
98 site_privatekey = CryptBitcoin.hdPrivatekey(self.master_seed, bip32_index)
99 site_address = CryptBitcoin.privatekeyToAddress(site_privatekey)
100 if site_address in self.sites:
101 raise Exception("Random error: site exist!")
102 # Save to sites
103 self.getSiteData(site_address)
104 self.sites[site_address]["privatekey"] = site_privatekey
105 self.save()
106 return site_address, bip32_index, self.sites[site_address]
107
108 # Get BIP32 address from site address
109 # Return: BIP32 auth address
110 def getAuthAddress(self, address, create=True):
111 cert = self.getCert(address)
112 if cert:
113 return cert["auth_address"]
114 else:
115 return self.getSiteData(address, create)["auth_address"]
116
117 def getAuthPrivatekey(self, address, create=True):
118 cert = self.getCert(address)
119 if cert:
120 return cert["auth_privatekey"]
121 else:
122 return self.getSiteData(address, create)["auth_privatekey"]
123
124 # Add cert for the user
125 def addCert(self, auth_address, domain, auth_type, auth_user_name, cert_sign):
126 # Find privatekey by auth address
127 auth_privatekey = [site["auth_privatekey"] for site in list(self.sites.values()) if site["auth_address"] == auth_address][0]
128 cert_node = {
129 "auth_address": auth_address,
130 "auth_privatekey": auth_privatekey,
131 "auth_type": auth_type,
132 "auth_user_name": auth_user_name,
133 "cert_sign": cert_sign
134 }
135 # Check if we have already cert for that domain and its not the same
136 if self.certs.get(domain) and self.certs[domain] != cert_node:
137 return False
138 elif self.certs.get(domain) == cert_node: # Same, not updated
139 return None
140 else: # Not exist yet, add
141 self.certs[domain] = cert_node
142 self.save()
143 return True
144
145 # Remove cert from user
146 def deleteCert(self, domain):
147 del self.certs[domain]
148
149 # Set active cert for a site
150 def setCert(self, address, domain):
151 site_data = self.getSiteData(address)
152 if domain:
153 site_data["cert"] = domain
154 else:
155 if "cert" in site_data:
156 del site_data["cert"]
157 self.saveDelayed()
158 return site_data
159
160 # Get cert for the site address
161 # Return: { "auth_address":.., "auth_privatekey":.., "auth_type": "web", "auth_user_name": "nofish", "cert_sign":.. } or None
162 def getCert(self, address):
163 site_data = self.getSiteData(address, create=False)
164 if not site_data or "cert" not in site_data:
165 return None # Site dont have cert
166 return self.certs.get(site_data["cert"])
167
168 # Get cert user name for the site address
169 # Return: user@certprovider.bit or None
170 def getCertUserId(self, address):
171 site_data = self.getSiteData(address, create=False)
172 if not site_data or "cert" not in site_data:
173 return None # Site dont have cert
174 cert = self.certs.get(site_data["cert"])
175 if cert:
176 return cert["auth_user_name"] + "@" + site_data["cert"]