async def onPeerAuthenticated(self, ipfsop, piCtx: PeerIdentityCtx): """ DID auth was successfull for a peer, store the handle and DID in the graph, and create the initial IPLD link for the DID document (diddoc) """ profile = ipfsop.ctx.currentProfile did = piCtx.ipid.did handle = str(piCtx.spaceHandle) async with profile.dagNetwork as g: pData = g.root['peers'].setdefault(piCtx.peerId, {}) if handle in pData: # This handle is already registered for this peer # This can happen if a peer created 2 IPIDs with # the same handle after losing the profile's data log.debug(f'{handle} already in the graph, overwriting') pData[handle] = { 'did': did, 'diddoc': {}, 'dateregistered': utcDatetimeIso(), 'datedidauth': utcDatetimeIso(), 'datelastseen': utcDatetimeIso(), 'flags': 0 } # Link the DID document in the graph if piCtx.ipid.docCid: pData[handle]['diddoc'] = g.ipld(piCtx.ipid.docCid) log.debug(f'Authenticated {handle} ({did}) in the peers graph')
async def createIdentity(self, ipfsop, peered=False, personDid=None, setAsCurrent=True, iphandle=None, bio=None, flags=None): # You never know .. uid = str(uuid.uuid4()) while uid in self.root['identities'].keys(): uid = str(uuid.uuid4()) pFlags = flags if isinstance(flags, int) else 0 qr = await self.encodeIpHandleQr(iphandle, personDid) bioEntry = await ipfsop.addString(bio if bio else '# Bio') identity = { 'email': None, 'flags': pFlags, 'iphandle': iphandle, 'iphandleqr': { 'png': self.mkLink(qr) }, 'vplanet': 'Earth', # DIDS 'personDid': personDid, 'following': {}, 'avatar': self.mkLink( await ipfsop.importQtResource('/share/icons/ipfs-cube-64.png')), 'bio': self.mkLink(bioEntry), 'crypto': {}, 'datecreated': utcDatetimeIso(), 'datemodified': utcDatetimeIso() } async with self as dag: dag.root['identities'][uid] = identity if setAsCurrent: dag.root['currentIdentityUid'] = uid log.debug('Created identity', uid) return uid, dag.root['identities'][uid]
async def a_iObjectCreate(self, app, loop, oid, obj): ipfsop = app.ipfsOperatorForLoop(loop) curProfile = ipfsop.ctx.currentProfile try: # Get the IPID and private key ipid = await curProfile.userInfo.ipIdentifier() assert ipid is not None passport = await ipid.searchServiceById( ipid.didUrl(path='/passport')) rsaAgent = await ipid.rsaAgentGet(ipfsop) privKey = await rsaAgent._privateKey() assert privKey is not None # Initial DAG structure dag = {"@context": {"@vocab": gLdBaseUri, "@base": gLdBaseUri}} dag.update(dict(obj)) if passport: # Author dag['author'] = { '@type': 'Person', '@id': passport.endpoint['me'].get('@id') } # Dates if 'dateCreated' not in dag: dag['dateCreated'] = utcDatetimeIso() if 'dateModified' not in dag: dag['dateModified'] = utcDatetimeIso() # JSON-LD signature signed = jsonldsig.sign(dag, privKey.exportKey()) # Store in IPFS cid = await ipfsop.dagPut(signed) # Notify the RDF service await runningApp().s.rdfStore(IPFSPath(cid)) # Return the CID of the object return cid except Exception as err: log.debug(f'Error creating object: {err}') return ''
async def writeStatus(self): client = self.daemonClient try: ident = await client.core.id() if self.process and ident: # Remember orjson.dumps returns bytes async with aiofiles.open(str(self.statusPath), 'w+b') as fd: await fd.write(orjson.dumps({ 'ident': ident, 'pid': self.process.pid, 'status': self.process.status(), 'date': utcDatetimeIso() })) return True else: raise Exception( "Could not get ident, what's going on ?") except aioipfs.APIError: return False except Exception as e: self.message(f'Status write error: {e}, postponing') return False
async def make(self, ipfsop, message='', links=[], attachments=[], date=None, type=CHATMSG_TYPE_MESSAGE, level=0): msgDate = date if date else utcDatetimeIso() msg = ChatRoomMessage({ '@context': await ipfsop.ldContextJson('messages/ChatRoomMessage'), 'msgtype': ChatRoomMessage.TYPE, 'version': 1, 'ChatRoomMessage': { 'chatmsgtype': type, 'date': msgDate, 'message': message, 'links': links, 'attachments': attachments, 'level': level } }) return msg
async def make(self, ipfsop, jwsCid, links=[], attachments=[], date=None, type=CHATMSG_TYPE_MESSAGE, command='MSG', params=[], level=0): msgDate = date if date else utcDatetimeIso() msg = ChatRoomMessage({ 'msgtype': ChatRoomMessage.TYPE, 'version': 1, 'ChatRoomMessage': { 'uid': uid4(), 'jwsTokenCid': jwsCid, 'chatmsgtype': type, 'date': msgDate, 'command': command, 'params': params, 'links': links, 'attachments': attachments, 'level': level } }) return msg
def make(): msg = SparQLHeartbeatMessage({ 'msgtype': SparQLHeartbeatMessage.TYPE, 'version': 1, 'date': utcDatetimeIso(), 'graphs': [] }) return msg
def pyramidAdd(self, pyramidPath, path, unique=False, type='mark'): sec, category, name = self.pyramidAccess(pyramidPath) if not sec: return False if type == 'mark': key = pyramidsMarksKey elif type == 'inputmark': key = pyramidsInputMarksKey else: raise ValueError('Invalid mark type') if name in sec[pyramidsKey]: pyramid = sec[pyramidsKey][name] count = len(pyramid[key]) if count >= pyramid.get('maxhashmarks', pyramidMaxHmarksDefault): pyramid[key].pop(0) # Don't register something that's already there if unique: for item in pyramid[key]: _marksPaths = item.keys() if path in _marksPaths: log.debug(f'Hashmark {path} already in pyramid {name}') return False exmark = self.find(path) if exmark: mark = copy.copy(exmark) else: datenowiso = utcDatetimeIso() mark = IPFSHashMark.make( path, title='{0}: #{1}'.format(name, count + 1), description=pyramid['description'], datecreated=datenowiso, share=False, pinSingle=True, icon=pyramid['icon'], ) pyramid[key].append(mark.data) pyramid['latest'] = path self.pyramidAddedMark.emit(pyramidPath, mark, type) self.pyramidChanged.emit(pyramidPath) if type == 'mark': self.pyramidCapstoned.emit(pyramidPath) self.changed.emit() return True return False
async def initDag(self, ipfsop): now = utcDatetimeIso() return { 'identities': {}, 'followingGlobal': {}, 'currentIdentityUid': None, 'datecreated': now, 'datemodified': now }
def make(name, ipfsMPath, title=None, ipnsResolveFrequency=3600): datecreated = utcDatetimeIso() mapping = QuickAccessMapping({ 'name': name, 'mappedto': str(ipfsMPath), 'ipnsresolvefreq': ipnsResolveFrequency, 'datecreated': datecreated, 'hotkey': None, 'title': title if title else name }) return mapping
async def addServiceCollection(self, ipfsop, name): return await self.addServiceContexted({ 'id': self.didUrl( path=posixpath.join('/collections', name) ), 'type': IPService.SRV_TYPE_COLLECTION, }, context='ObjectsCollectionEndpoint', endpoint={ 'name': name, 'created': utcDatetimeIso(), 'objects': [] }, publish=True)
async def make(self, ipfsop, status): msg = ChatStatusMessage({ '@context': await ipfsop.ldContextJson('messages/ChatStatusMessage'), 'msgtype': ChatStatusMessage.TYPE, 'ChatStatusMessage': { 'status': status, 'date': utcDatetimeIso() } }) return msg
async def addServiceVideoCall(self, ipfsop, roomName): servicePath = posixpath.join('videocall', roomName) return await self.addServiceContexted({ 'id': self.didUrl( path=servicePath ), 'type': IPService.SRV_TYPE_VIDEOCALL, }, context='DwebVideoCallServiceEndpoint', endpoint={ 'roomName': roomName, 'created': utcDatetimeIso(), 'p2pEndpoint': ipfsop.p2pEndpoint(servicePath) }, publish=True)
async def ipfsSave(self, op): self.debug('Saving (acquiring lock)') async with self.wLock: prevCid = self.dagCid history = self.dagMeta.setdefault('history', []) maxItems = self.dagMetaMaxHistoryItems if prevCid and self._autoPrevious: # Create a 'previous' IPLD link self.dagRoot['previous'] = self.mkLink(prevCid) if isinstance(self.dagRoot, dict) and self._autoUpdateDates: # Update 'datemodified' if enabled if 'datemodified' in self.dagRoot: self.dagRoot['datemodified'] = utcDatetimeIso() # We always PIN the latest DAG and do a pin update using the # previous item in the history cid = await op.dagPut(self.dagRoot, pin=True, offline=self._offline) if cid is not None: if prevCid is not None and prevCid not in history: if len(history) > maxItems: # Purge old items [history.pop() for idx in range( 0, len(history) - maxItems)] history.insert(0, prevCid) if 0: # pinUpdate randomly blocks so # disable it for now await op.pinUpdate( prevCid, cid, unpin=self._unpinOnUpdate) # Save the new CID and update the metadata await self.saveNewCid(cid) else: # Bummer self.debug('DAG could not be built') return False self.debug('Saved (wlock released)') return True
def make(iGraphList): msg = RDFGraphsExchangeMessage({ 'msgtype': RDFGraphsExchangeMessage.TYPE, 'version': 1, 'date': utcDatetimeIso(), 'graphs': [] }) for name, graph in iGraphList.items(): if not graph.cid: continue msg.data['graphs'].append({ 'graphExportCid': graph.cid, 'graphUri': str(graph.identifier), 'format': 'ttl' }) return msg
async def importHashmark(mark, source): url = mark.get('url') datecreated = mark.get('datecreated', utcDatetimeIso()) log.debug('Importing {u} ({date})'.format(u=url, date=datecreated)) return await database.hashmarkAdd( url, title=mark.get('title'), description=mark.get('description'), comment=mark.get('comment'), category=mark.get('category'), icon=mark.get('icon'), datecreated=datecreated, tags=mark.get('tags', []), objtags=mark.get('objtags', []), source=source, schemepreferred=mark.get('schemepreferred'))
def make(path, title=None, datecreated=None, share=False, description='', comment='', datasize=None, cumulativesize=None, srcplanet='Earth', tags=None, numlinks=None, icon=None, pinSingle=False, pinRecursive=False): if datecreated is None: datecreated = utcDatetimeIso() mData = IPFSHashMark({ path: { 'metadata': { 'title': title, 'description': description, 'comment': comment, 'srcplanet': srcplanet, 'datasize': datasize, 'cumulativesize': cumulativesize, 'numlinks': numlinks, }, 'pin': { 'single': pinSingle, 'recursive': pinRecursive, 'filters': [] }, 'datecreated': datecreated, 'tscreated': int(time.time()), 'icon': icon, 'share': share, 'tags': tags if tags else [], } }) mData.path = path return mData
def make(name, description='Pyramid', icon=None, ipnskey=None, internal=False, publishdelay=0, comment=None, flags=0, allowoffline=False, lifetime='48h', maxhashmarks=pyramidMaxHmarksDefault, type=None, extra=None): extraOpts = extra if isinstance(extra, dict) else {} datecreated = utcDatetimeIso() pyramid = MultihashPyramid({ name: { 'ipns': { 'publishtokey': ipnskey, 'publishdelay': publishdelay, 'allowoffline': allowoffline, 'ttl': None, 'lifetime': lifetime }, 'uuid': str(uuid.uuid4()), 'datecreated': datecreated, 'icon': icon, 'latest': None, 'description': description, 'comment': comment, 'type': type if type else MultihashPyramid.TYPE_STANDARD, 'internal': internal, 'maxhashmarks': maxhashmarks, 'flags': flags, 'extra': extraOpts, pyramidsInputMarksKey: [], pyramidsMarksKey: [] # list of hashmarks in the pyramid } }) pyramid.name = name return pyramid
async def didPing(self, ipfsop, request): async with self.throttler: curProfile = ipfsop.ctx.currentProfile if not curProfile: return await self.msgError() try: js = await request.json() if not js or not isinstance(js, dict): return await self.msgError() if not jsonSchemaValidate(js, pingReqSchema): raise Exception('Invalid req schema') token = js.get('ident_token') if token != self.service._token: self.message(f'didPing: Invalid token {token}') raise Exception('Invalid token') except Exception: return await self.msgError() did = js.get('did') self.message('Received didPing request for DID: {}'.format( did)) currentIpid = await curProfile.userInfo.ipIdentifier() if not currentIpid or did != currentIpid.did: return await self.msgError(error='Invalid DID') return web.json_response({ 'didpong': { 'version': 0, did: { 'userstatus': curProfile.status, 'userstatusmessage': curProfile.statusMessage, 'date': utcDatetimeIso() } } })
def make(revision: str, dagClass: str, dagCid: str, dagNet: str, dagName: str, dagUid: str, signerPubKeyCid: str, mDagCid: str, serviceToken: str, snakeOil: str): return DAGExchangeMessage({ 'msgtype': DAGExchangeMessage.TYPE, 'version': 2, 'date': utcDatetimeIso(), 'rev': revision, 'exchange': { 'dagCid': dagCid, 'megaDagCid': mDagCid, 'dagClass': dagClass, 'dagNet': dagNet, 'dagName': dagName, 'dagUid': dagUid, 'signerPubKeyCid': signerPubKeyCid, 'session': { 'serviceToken': serviceToken, 'snakeOil': snakeOil } } })
async def trace(self, iPath: IPFSPath, graphs: list): """ Main history API (trace an object in the history graph) """ h = hashlib.sha1() h.update(iPath.ipfsUrl.encode()) nodeId = f'io:{h.hexdigest()}' doc = { '@context': { '@vocab': 'ips://galacteek.ld/' }, '@type': 'GraphTrail', '@id': nodeId, 'dateCreated': utcDatetimeIso(), 'ipfsPath': str(iPath), 'outputGraphs': graphs } result = list(self.hGraph.predicate_objects(nodeId)) if result: return try: ex = await jsonld.expand(doc) graph = BaseGraph() graph.parse(data=orjson.dumps(ex).decode(), format='json-ld') # Could be optimized using another rdflib method self.hGraph.parse(await graph.ttlize()) ttl = await self.hGraph.ttlize() print(ttl.decode()) except Exception as err: log.debug(f'Error recording {iPath} in history: {err}')
async def make(ipfsop, channel, pubKeyCid, encType='rsa-aes256-cbc'): curProfile = ipfsop.ctx.currentProfile try: hasher = hashlib.sha3_384() hasher.update('{peer}_{uid}'.format( peer=ipfsop.ctx.node.id, uid=doubleUid4()).encode()) topic = encChatChannelTopic(hasher.hexdigest()) except Exception: return None if encType == 'rsa-aes256-cbc': enc = { 'etype': encType, 'pubKeyCid': await ipfsop.rsaAgent.pubKeyCid() } elif encType == 'curve25519': enc = { 'etype': encType, 'pubKeyCid': pubKeyCid } else: enc = {} return ChatToken({ 'version': 1, 'type': 'pubchattoken', 't': { 'date': utcDatetimeIso(), 'did': curProfile.userInfo.personDid, 'channel': channel, 'psTopic': topic, 'pubKeyCid': await ipfsop.rsaAgent.pubKeyCid(), 'cpass': secrets.token_hex(8), 'enc': enc } })
async def initDag(self, ipfsop): return { 'peers': {}, 'datecreated': utcDatetimeIso(), 'datemodified': utcDatetimeIso() }
async def seed(self, ipfsop, name: str, objectsInfo: list, section='all', description='', comment='', revision=0, icon=None, authorName=None, authorEmail=None, authorDid=None, creatorDid=None, creatorIpHandle=None, licenseCid=None, licenseName=None, manifestCid=None, cumulativeSize=0, pinReqMin=10, pinReqTarget=20, tags=[], keywords=[], refid=None): profile = ipfsop.ctx.currentProfile ipid = await profile.userInfo.ipIdentifier() if not ipid: raise Exception('No IPID found') uid = doubleUid4() signedUid = await ipfsop.rsaAgent.pssSign64(uid.encode()) preSeed = { 'formatv': 0, 'seed': { 'name': name, 'uid': uid, 'revision': revision, 'tags': tags, 'dateCreated': utcDatetimeIso(), 'dateModified': utcDatetimeIso(), 'icon': self.ipld(icon) if icon else None, 'license': { 'name': licenseName, 'link': self.ipld(licenseCid) if licenseCid else None }, 'author': { 'name': authorName, 'email': authorEmail, 'did': authorDid }, 'wallets': {}, 'creatorDid': creatorDid, 'creatorIpHandle': creatorIpHandle, 'description': description, 'comment': comment, 'manifests': [], 'pinRequest': { 'minproviders': pinReqMin, 'targetproviders': pinReqTarget, 'p2pPinServiceUrl': None }, 'objects': objectsInfo, 'cumulativeSize': cumulativeSize }, 'parent': None, 'signatures': { 'pss_uid': signedUid, } } if manifestCid: preSeed['seed']['manifests'].append(self.ipld(manifestCid)) seedCid = await ipfsop.dagPut(preSeed, pin=True) if seedCid: try: cidSign = await ipfsop.rsaAgent.pssSign(seedCid.encode()) if not cidSign: raise Exception('Could not sign CID') cidSignEntry = await ipfsop.addBytes(cidSign) if not cidSignEntry: raise Exception('Could not import CID signature') async with self as dagw: s = dagw.root['c']['seeds'].setdefault(section, {}) cur = s.setdefault(name, []) # We store some metadata to make it easier to search # without loading the full seed DAG cur.append({ '_metadata': { 'datecreated': utcDatetimeIso(), 'dateexpires': None, 'icon': self.ipld(icon) if icon else None, 'tags': tags, 'keywords': keywords, 'refid': refid, 'pss_cid_link': dagw.ipld(cidSignEntry) }, 'seedlink': dagw.ipld(seedCid) }) # Regenerate an iteration UID dagw.root['params']['revnum'] += 1 dagw.root['params']['seediteruid'] = doubleUid4() # Sign the iteration signedIter = await ipfsop.rsaAgent.pssSign64( dagw.root['params']['seediteruid'].encode()) dagw.root['signatures']['iteration'] = signedIter await self.sSeedAdded.emit(seedCid) return seedCid except Exception as err: log.debug(f'Error creating seed: {err}') raise err
async def link(self, ipfsop, peerId: str, dagUid: str, dagCid: str, signerPubKeyCid: str, local=False): self.debug(f'Branching for {peerId}') if not local: if not await ipfsop.pin(dagCid, recursive=False, timeout=120): self.debug(f'Branching for {peerId}: PIN {dagCid}: FAILED') return False else: self.debug(f'Branching for {peerId}: PIN {dagCid}: OK') pubKeyStatInfo = await ipfsop.objStatInfo(signerPubKeyCid) if not pubKeyStatInfo.valid or \ pubKeyStatInfo.dataLargerThan(kilobytes(32)): return False pubKeyPem = await ipfsop.catObject(signerPubKeyCid, timeout=10) if not pubKeyPem: raise Exception(f'Cannot fetch pubkey with CID: {signerPubKeyCid}') # Keep it await ipfsop.pin(signerPubKeyCid, recursive=False, timeout=5) valid = await ipfsop.waitFor(self.analyze(peerId, dagCid, pubKeyPem), 60) if not valid: self.debug(f'Invalid DAG: {dagCid}') return False else: self.debug(f'DAG is valid: {dagCid}') linkId = self.udbHash(peerId, dagUid) r = await self.resolve(f'nodes/{linkId}/link') self.debug(f'Branching for {peerId}: has {r}') if r and stripIpfs(r) == dagCid: self.debug(f'Branching for {peerId}: already at latest') return False # Link it async with self as dag: dag.root['nodes'][linkId] = { 'datebranched': utcDatetimeIso(), 'signerpubkey': self.ipld(signerPubKeyCid), 'link': self.ipld(dagCid) } dag.root['data']['aggiter_uid'] = str(uuid.uuid4()) sig = await ipfsop.ctx.rsaAgent.pssSign64( dag.root['data']['aggiter_uid'].encode()) if sig: dag.root['signatures']['aggiter'] = sig # Link OK return True