123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675 |
- __filename__ = "person.py"
- __author__ = "Bob Mottram"
- __license__ = "AGPL3+"
- __version__ = "1.0.0"
- __maintainer__ = "Bob Mottram"
- __email__ = "bob@freedombone.net"
- __status__ = "Production"
- import json
- import commentjson
- import os
- import fileinput
- import subprocess
- import shutil
- from pprint import pprint
- from pathlib import Path
- from Crypto.PublicKey import RSA
- from shutil import copyfile
- from webfinger import createWebfingerEndpoint
- from webfinger import storeWebfingerEndpoint
- from posts import createDMTimeline
- from posts import createInbox
- from posts import createOutbox
- from posts import createModeration
- from auth import storeBasicCredentials
- from auth import removePassword
- from roles import setRole
- from media import removeMetaData
- from utils import validNickname
- from utils import noOfAccounts
- from auth import createPassword
- from config import setConfigParam
- from config import getConfigParam
- def generateRSAKey() -> (str,str):
- key = RSA.generate(2048)
- privateKeyPem = key.exportKey("PEM").decode("utf-8")
- publicKeyPem = key.publickey().exportKey("PEM").decode("utf-8")
- return privateKeyPem,publicKeyPem
- def setProfileImage(baseDir: str,httpPrefix :str,nickname: str,domain: str, \
- port :int,imageFilename: str,imageType :str,resolution :str) -> bool:
- """Saves the given image file as an avatar or background
- image for the given person
- """
- imageFilename=imageFilename.replace('\n','')
- if not (imageFilename.endswith('.png') or \
- imageFilename.endswith('.jpg') or \
- imageFilename.endswith('.jpeg') or \
- imageFilename.endswith('.gif')):
- print('Profile image must be png, jpg or gif format')
- return False
- if imageFilename.startswith('~/'):
- imageFilename=imageFilename.replace('~/',str(Path.home())+'/')
- if ':' in domain:
- domain=domain.split(':')[0]
- fullDomain=domain
- if port:
- if port!=80 and port!=443:
- if ':' not in domain:
- fullDomain=domain+':'+str(port)
- handle=nickname.lower()+'@'+domain.lower()
- personFilename=baseDir+'/accounts/'+handle+'.json'
- if not os.path.isfile(personFilename):
- print('person definition not found: '+personFilename)
- return False
- if not os.path.isdir(baseDir+'/accounts/'+handle):
- print('Account not found: '+baseDir+'/accounts/'+handle)
- return False
- iconFilenameBase='icon'
- if imageType=='avatar' or imageType=='icon':
- iconFilenameBase='icon'
- else:
- iconFilenameBase='image'
-
- mediaType='image/png'
- iconFilename=iconFilenameBase+'.png'
- if imageFilename.endswith('.jpg') or \
- imageFilename.endswith('.jpeg'):
- mediaType='image/jpeg'
- iconFilename=iconFilenameBase+'.jpg'
- if imageFilename.endswith('.gif'):
- mediaType='image/gif'
- iconFilename=iconFilenameBase+'.gif'
- profileFilename=baseDir+'/accounts/'+handle+'/'+iconFilename
- with open(personFilename, 'r') as fp:
- personJson=commentjson.load(fp)
- personJson[iconFilenameBase]['mediaType']=mediaType
- personJson[iconFilenameBase]['url']=httpPrefix+'://'+fullDomain+'/users/'+nickname+'/'+iconFilename
- with open(personFilename, 'w') as fp:
- commentjson.dump(personJson, fp, indent=4, sort_keys=False)
-
- cmd = '/usr/bin/convert '+imageFilename+' -size '+resolution+' -quality 50 '+profileFilename
- subprocess.call(cmd, shell=True)
- removeMetaData(profileFilename,profileFilename)
- return True
- return False
- def setOrganizationScheme(baseDir: str,nickname: str,domain: str, \
- schema: str) -> bool:
- """Set the organization schema within which a person exists
- This will define how roles, skills and availability are assembled
- into organizations
- """
- # avoid giant strings
- if len(schema)>256:
- return False
- actorFilename=baseDir+'/accounts/'+nickname+'@'+domain+'.json'
- if not os.path.isfile(actorFilename):
- return False
- with open(actorFilename, 'r') as fp:
- actorJson=commentjson.load(fp)
- actorJson['orgSchema']=schema
- with open(actorFilename, 'w') as fp:
- commentjson.dump(actorJson, fp, indent=4, sort_keys=False)
- return True
- def accountExists(baseDir: str,nickname: str,domain: str) -> bool:
- """Returns true if the given account exists
- """
- if ':' in domain:
- domain=domain.split(':')[0]
- return os.path.isdir(baseDir+'/accounts/'+nickname+'@'+domain)
- def createPersonBase(baseDir: str,nickname: str,domain: str,port: int, \
- httpPrefix: str, saveToFile: bool,password=None) -> (str,str,{},{}):
- """Returns the private key, public key, actor and webfinger endpoint
- """
- privateKeyPem,publicKeyPem=generateRSAKey()
- webfingerEndpoint= \
- createWebfingerEndpoint(nickname,domain,port,httpPrefix,publicKeyPem)
- if saveToFile:
- storeWebfingerEndpoint(nickname,domain,port,baseDir,webfingerEndpoint)
- handle=nickname.lower()+'@'+domain.lower()
- originalDomain=domain
- if port:
- if port!=80 and port!=443:
- if ':' not in domain:
- domain=domain+':'+str(port)
- personType='Person'
- approveFollowers=False
- personName=nickname
- personId=httpPrefix+'://'+domain+'/users/'+nickname
- inboxStr=personId+'/inbox'
- personUrl=httpPrefix+'://'+domain+'/@'+personName
- if nickname=='inbox':
- # shared inbox
- inboxStr=httpPrefix+'://'+domain+'/actor/inbox'
- personId=httpPrefix+'://'+domain+'/actor'
- personUrl=httpPrefix+'://'+domain+'/about/more?instance_actor=true'
- personName=originalDomain
- approveFollowers=True
- personType='Application'
- newPerson = {'@context': ['https://www.w3.org/ns/activitystreams',
- 'https://w3id.org/security/v1',
- {'Emoji': 'toot:Emoji',
- 'Hashtag': 'as:Hashtag',
- 'IdentityProof': 'toot:IdentityProof',
- 'PropertyValue': 'schema:PropertyValue',
- 'alsoKnownAs': {'@id': 'as:alsoKnownAs', '@type': '@id'},
- 'focalPoint': {'@container': '@list', '@id': 'toot:focalPoint'},
- 'manuallyApprovesFollowers': 'as:manuallyApprovesFollowers',
- 'movedTo': {'@id': 'as:movedTo', '@type': '@id'},
- 'schema': 'http://schema.org#',
- 'value': 'schema:value'}],
- 'attachment': [],
- 'endpoints': {
- 'id': personId+'/endpoints',
- 'sharedInbox': httpPrefix+'://'+domain+'/inbox',
- },
- 'capabilityAcquisitionEndpoint': httpPrefix+'://'+domain+'/caps/new',
- 'followers': personId+'/followers',
- 'following': personId+'/following',
- 'shares': personId+'/shares',
- 'orgSchema': None,
- 'skills': {},
- 'roles': {},
- 'availability': None,
- 'icon': {'mediaType': 'image/png',
- 'type': 'Image',
- 'url': personId+'/avatar.png'},
- 'id': personId,
- 'image': {'mediaType': 'image/png',
- 'type': 'Image',
- 'url': personId+'/image.png'},
- 'inbox': inboxStr,
- 'manuallyApprovesFollowers': approveFollowers,
- 'name': personName,
- 'outbox': personId+'/outbox',
- 'preferredUsername': personName,
- 'summary': '',
- 'publicKey': {
- 'id': personId+'#main-key',
- 'owner': personId,
- 'publicKeyPem': publicKeyPem
- },
- 'tag': [],
- 'type': personType,
- 'url': personUrl
- }
- if nickname=='inbox':
- # fields not needed by the shared inbox
- del newPerson['outbox']
- del newPerson['icon']
- del newPerson['image']
- del newPerson['skills']
- del newPerson['shares']
- del newPerson['roles']
- del newPerson['tag']
- del newPerson['availability']
- del newPerson['followers']
- del newPerson['following']
- del newPerson['attachment']
- if saveToFile:
- # save person to file
- peopleSubdir='/accounts'
- if not os.path.isdir(baseDir+peopleSubdir):
- os.mkdir(baseDir+peopleSubdir)
- if not os.path.isdir(baseDir+peopleSubdir+'/'+handle):
- os.mkdir(baseDir+peopleSubdir+'/'+handle)
- if not os.path.isdir(baseDir+peopleSubdir+'/'+handle+'/inbox'):
- os.mkdir(baseDir+peopleSubdir+'/'+handle+'/inbox')
- if not os.path.isdir(baseDir+peopleSubdir+'/'+handle+'/outbox'):
- os.mkdir(baseDir+peopleSubdir+'/'+handle+'/outbox')
- if not os.path.isdir(baseDir+peopleSubdir+'/'+handle+'/ocap'):
- os.mkdir(baseDir+peopleSubdir+'/'+handle+'/ocap')
- if not os.path.isdir(baseDir+peopleSubdir+'/'+handle+'/queue'):
- os.mkdir(baseDir+peopleSubdir+'/'+handle+'/queue')
- filename=baseDir+peopleSubdir+'/'+handle+'.json'
- with open(filename, 'w') as fp:
- commentjson.dump(newPerson, fp, indent=4, sort_keys=False)
- # save to cache
- if not os.path.isdir(baseDir+'/cache'):
- os.mkdir(baseDir+'/cache')
- if not os.path.isdir(baseDir+'/cache/actors'):
- os.mkdir(baseDir+'/cache/actors')
- cacheFilename=baseDir+'/cache/actors/'+newPerson['id'].replace('/','#')+'.json'
- with open(cacheFilename, 'w') as fp:
- commentjson.dump(newPerson, fp, indent=4, sort_keys=False)
- # save the private key
- privateKeysSubdir='/keys/private'
- if not os.path.isdir(baseDir+'/keys'):
- os.mkdir(baseDir+'/keys')
- if not os.path.isdir(baseDir+privateKeysSubdir):
- os.mkdir(baseDir+privateKeysSubdir)
- filename=baseDir+privateKeysSubdir+'/'+handle+'.key'
- with open(filename, "w") as text_file:
- print(privateKeyPem, file=text_file)
- # save the public key
- publicKeysSubdir='/keys/public'
- if not os.path.isdir(baseDir+publicKeysSubdir):
- os.mkdir(baseDir+publicKeysSubdir)
- filename=baseDir+publicKeysSubdir+'/'+handle+'.pem'
- with open(filename, "w") as text_file:
- print(publicKeyPem, file=text_file)
- if password:
- storeBasicCredentials(baseDir,nickname,password)
- return privateKeyPem,publicKeyPem,newPerson,webfingerEndpoint
- def registerAccount(baseDir: str,httpPrefix: str,domain: str,port: int, \
- nickname: str,password: str) -> bool:
- """Registers a new account from the web interface
- """
- if accountExists(baseDir,nickname,domain):
- return False
- if not validNickname(domain,nickname):
- print('REGISTER: Nickname '+nickname+' is invalid')
- return False
- if len(password)<8:
- print('REGISTER: Password should be at least 8 characters')
- return False
- privateKeyPem,publicKeyPem,newPerson,webfingerEndpoint= \
- createPerson(baseDir,nickname,domain,port, \
- httpPrefix,True,password)
- if privateKeyPem:
- return True
- return False
- def createPerson(baseDir: str,nickname: str,domain: str,port: int, \
- httpPrefix: str, saveToFile: bool,password=None) -> (str,str,{},{}):
- """Returns the private key, public key, actor and webfinger endpoint
- """
- if not validNickname(domain,nickname):
- return None,None,None,None
- # If a config.json file doesn't exist then don't decrement
- # remaining registrations counter
- remainingConfigExists=getConfigParam(baseDir,'registrationsRemaining')
- if remainingConfigExists:
- registrationsRemaining=int(remainingConfigExists)
- if registrationsRemaining<=0:
- return None,None,None,None
- privateKeyPem,publicKeyPem,newPerson,webfingerEndpoint = \
- createPersonBase(baseDir,nickname,domain,port,httpPrefix,saveToFile,password)
- if noOfAccounts(baseDir)==1:
- #print(nickname+' becomes the instance admin and a moderator')
- setRole(baseDir,nickname,domain,'instance','admin')
- setRole(baseDir,nickname,domain,'instance','moderator')
- setRole(baseDir,nickname,domain,'instance','delegator')
- setConfigParam(baseDir,'admin',nickname)
- if not os.path.isdir(baseDir+'/accounts'):
- os.mkdir(baseDir+'/accounts')
- if not os.path.isdir(baseDir+'/accounts/'+nickname+'@'+domain):
- os.mkdir(baseDir+'/accounts/'+nickname+'@'+domain)
-
- if os.path.isfile(baseDir+'/img/default-avatar.png'):
- copyfile(baseDir+'/img/default-avatar.png',baseDir+'/accounts/'+nickname+'@'+domain+'/avatar.png')
- if os.path.isfile(baseDir+'/img/image.png'):
- copyfile(baseDir+'/img/image.png',baseDir+'/accounts/'+nickname+'@'+domain+'/image.png')
- if os.path.isfile(baseDir+'/img/banner.png'):
- copyfile(baseDir+'/img/banner.png',baseDir+'/accounts/'+nickname+'@'+domain+'/banner.png')
- if remainingConfigExists:
- registrationsRemaining-=1
- setConfigParam(baseDir,'registrationsRemaining',str(registrationsRemaining))
- return privateKeyPem,publicKeyPem,newPerson,webfingerEndpoint
- def createSharedInbox(baseDir: str,nickname: str,domain: str,port: int, \
- httpPrefix: str) -> (str,str,{},{}):
- """Generates the shared inbox
- """
- return createPersonBase(baseDir,nickname,domain,port,httpPrefix,True,None)
- def createCapabilitiesInbox(baseDir: str,nickname: str,domain: str,port: int, \
- httpPrefix: str) -> (str,str,{},{}):
- """Generates the capabilities inbox to sign requests
- """
- return createPersonBase(baseDir,nickname,domain,port,httpPrefix,True,None)
-
- def personLookup(domain: str,path: str,baseDir: str) -> {}:
- """Lookup the person for an given nickname
- """
- if path.endswith('#main-key'):
- path=path.replace('#main-key','')
- # is this a shared inbox lookup?
- isSharedInbox=False
- if path=='/inbox' or path=='/users/inbox' or path=='/sharedInbox':
- # shared inbox actor on @domain@domain
- path='/users/'+domain
- isSharedInbox=True
- else:
- notPersonLookup=['/inbox','/outbox','/outboxarchive', \
- '/followers','/following','/featured', \
- '.png','.jpg','.gif','.mpv']
- for ending in notPersonLookup:
- if path.endswith(ending):
- return None
- nickname=None
- if path.startswith('/users/'):
- nickname=path.replace('/users/','',1)
- if path.startswith('/@'):
- nickname=path.replace('/@','',1)
- if not nickname:
- return None
- if not isSharedInbox and not validNickname(domain,nickname):
- return None
- if ':' in domain:
- domain=domain.split(':')[0]
- handle=nickname+'@'+domain
- filename=baseDir+'/accounts/'+handle+'.json'
- if not os.path.isfile(filename):
- return None
- personJson={"user": "unknown"}
- try:
- with open(filename, 'r') as fp:
- personJson=commentjson.load(fp)
- except:
- print('WARN: Failed to load actor '+filename)
- return None
- return personJson
- def personBoxJson(baseDir: str,domain: str,port: int,path: str, \
- httpPrefix: str,noOfItems: int,boxname: str, \
- authorized: bool,ocapAlways: bool) -> []:
- """Obtain the inbox/outbox/moderation feed for the given person
- """
- if boxname!='inbox' and boxname!='dm' and \
- boxname!='outbox' and boxname!='moderation':
- return None
- if not '/'+boxname in path:
- return None
- # Only show the header by default
- headerOnly=True
- # handle page numbers
- pageNumber=None
- if '?page=' in path:
- pageNumber=path.split('?page=')[1]
- if pageNumber=='true':
- pageNumber=1
- else:
- try:
- pageNumber=int(pageNumber)
- except:
- pass
- path=path.split('?page=')[0]
- headerOnly=False
- if not path.endswith('/'+boxname):
- return None
- nickname=None
- if path.startswith('/users/'):
- nickname=path.replace('/users/','',1).replace('/'+boxname,'')
- if path.startswith('/@'):
- nickname=path.replace('/@','',1).replace('/'+boxname,'')
- if not nickname:
- return None
- if not validNickname(domain,nickname):
- return None
- if boxname=='inbox':
- return createInbox(baseDir,nickname,domain,port,httpPrefix, \
- noOfItems,headerOnly,ocapAlways,pageNumber)
- if boxname=='dm':
- return createDMTimeline(baseDir,nickname,domain,port,httpPrefix, \
- noOfItems,headerOnly,ocapAlways,pageNumber)
- elif boxname=='outbox':
- return createOutbox(baseDir,nickname,domain,port,httpPrefix, \
- noOfItems,headerOnly,authorized,pageNumber)
- elif boxname=='moderation':
- return createModeration(baseDir,nickname,domain,port,httpPrefix, \
- noOfItems,headerOnly,authorized,pageNumber)
- return None
- def personInboxJson(baseDir: str,domain: str,port: int,path: str, \
- httpPrefix: str,noOfItems: int,ocapAlways: bool) -> []:
- """Obtain the inbox feed for the given person
- Authentication is expected to have already happened
- """
- if not '/inbox' in path:
- return None
- # Only show the header by default
- headerOnly=True
- # handle page numbers
- pageNumber=None
- if '?page=' in path:
- pageNumber=path.split('?page=')[1]
- if pageNumber=='true':
- pageNumber=1
- else:
- try:
- pageNumber=int(pageNumber)
- except:
- pass
- path=path.split('?page=')[0]
- headerOnly=False
- if not path.endswith('/inbox'):
- return None
- nickname=None
- if path.startswith('/users/'):
- nickname=path.replace('/users/','',1).replace('/inbox','')
- if path.startswith('/@'):
- nickname=path.replace('/@','',1).replace('/inbox','')
- if not nickname:
- return None
- if not validNickname(domain,nickname):
- return None
- return createInbox(baseDir,nickname,domain,port,httpPrefix, \
- noOfItems,headerOnly,ocapAlways,pageNumber)
- def setDisplayNickname(baseDir: str,nickname: str, domain: str, \
- displayName: str) -> bool:
- if len(displayName)>32:
- return False
- handle=nickname.lower()+'@'+domain.lower()
- filename=baseDir+'/accounts/'+handle.lower()+'.json'
- if not os.path.isfile(filename):
- return False
- personJson=None
- with open(filename, 'r') as fp:
- personJson=commentjson.load(fp)
- if not personJson:
- return False
- personJson['name']=displayName
- with open(filename, 'w') as fp:
- commentjson.dump(personJson, fp, indent=4, sort_keys=False)
- return True
- def setBio(baseDir: str,nickname: str, domain: str, bio: str) -> bool:
- if len(bio)>32:
- return False
- handle=nickname.lower()+'@'+domain.lower()
- filename=baseDir+'/accounts/'+handle.lower()+'.json'
- if not os.path.isfile(filename):
- return False
- personJson=None
- with open(filename, 'r') as fp:
- personJson=commentjson.load(fp)
- if not personJson:
- return False
- if not personJson.get('summary'):
- return False
- personJson['summary']=bio
- with open(filename, 'w') as fp:
- commentjson.dump(personJson, fp, indent=4, sort_keys=False)
- return True
- def isSuspended(baseDir: str,nickname: str) -> bool:
- """Returns true if the given nickname is suspended
- """
- adminNickname=getConfigParam(baseDir,'admin')
- if nickname==adminNickname:
- return False
- suspendedFilename=baseDir+'/accounts/suspended.txt'
- if os.path.isfile(suspendedFilename):
- with open(suspendedFilename, "r") as f:
- lines = f.readlines()
- suspendedFile=open(suspendedFilename,"w+")
- for suspended in lines:
- if suspended.strip('\n')==nickname:
- return True
- return False
- def unsuspendAccount(baseDir: str,nickname: str) -> None:
- """Removes an account suspention
- """
- suspendedFilename=baseDir+'/accounts/suspended.txt'
- if os.path.isfile(suspendedFilename):
- with open(suspendedFilename, "r") as f:
- lines = f.readlines()
- suspendedFile=open(suspendedFilename,"w+")
- for suspended in lines:
- if suspended.strip('\n')!=nickname:
- suspendedFile.write(suspended)
- suspendedFile.close()
- def suspendAccount(baseDir: str,nickname: str,salts: {}) -> None:
- """Suspends the given account
- This also changes the salt used by the authentication token
- so that the person can't continue to use the system without
- going through the login screen
- """
- # Don't suspend the admin
- adminNickname=getConfigParam(baseDir,'admin')
- if nickname==adminNickname:
- return
- # Don't suspend moderators
- moderatorsFile=baseDir+'/accounts/moderators.txt'
- if os.path.isfile(moderatorsFile):
- with open(moderatorsFile, "r") as f:
- lines = f.readlines()
- for moderator in lines:
- if moderator.strip('\n')==nickname:
- return
- suspendedFilename=baseDir+'/accounts/suspended.txt'
- if os.path.isfile(suspendedFilename):
- with open(suspendedFilename, "r") as f:
- lines = f.readlines()
- for suspended in lines:
- if suspended.strip('\n')==nickname:
- return
- suspendedFile=open(suspendedFilename,'a+')
- if suspendedFile:
- suspendedFile.write(nickname+'\n')
- suspendedFile.close()
- salts[nickname]=createPassword(32)
- else:
- suspendedFile=open(suspendedFilename,'w+')
- if suspendedFile:
- suspendedFile.write(nickname+'\n')
- suspendedFile.close()
- salts[nickname]=createPassword(32)
- def canRemovePost(baseDir: str,nickname: str,domain: str,port: int,postId: str) -> bool:
- """Returns true if the given post can be removed
- """
- if '/statuses/' not in postId:
- return False
- domainFull=domain
- if port:
- if port!=80 and port!=443:
- if ':' not in domain:
- domainFull=domain+':'+str(port)
- # is the post by the admin?
- adminNickname=getConfigParam(baseDir,'admin')
- if domainFull+'/users/'+adminNickname+'/' in postId:
- return False
- # is the post by a moderator?
- moderatorsFile=baseDir+'/accounts/moderators.txt'
- if os.path.isfile(moderatorsFile):
- with open(moderatorsFile, "r") as f:
- lines = f.readlines()
- for moderator in lines:
- if domainFull+'/users/'+moderator.strip('\n')+'/' in postId:
- return False
- return True
- def removeTagsForNickname(baseDir: str,nickname: str,domain: str,port: int) -> None:
- """Removes tags for a nickname
- """
- if not os.path.isdir(baseDir+'/tags'):
- return
- domainFull=domain
- if port:
- if port!=80 and port!=443:
- if ':' not in domain:
- domainFull=domain+':'+str(port)
- matchStr=domainFull+'/users/'+nickname+'/'
- directory = os.fsencode(baseDir+'/tags/')
- for f in os.listdir(directory):
- filename = os.fsdecode(f)
- if not filename.endswith(".txt"):
- continue
- tagFilename=os.path.join(baseDir+'/accounts/',filename)
- if matchStr not in open(tagFilename).read():
- continue
- with open(tagFilename, "r") as f:
- lines = f.readlines()
- tagFile=open(tagFilename,"w+")
- if tagFile:
- for tagline in lines:
- if matchStr not in tagline:
- tagFile.write(tagline)
- tagFile.close()
- def removeAccount(baseDir: str,nickname: str,domain: str,port: int) -> bool:
- """Removes an account
- """
- # Don't remove the admin
- adminNickname=getConfigParam(baseDir,'admin')
- if nickname==adminNickname:
- return False
- # Don't remove moderators
- moderatorsFile=baseDir+'/accounts/moderators.txt'
- if os.path.isfile(moderatorsFile):
- with open(moderatorsFile, "r") as f:
- lines = f.readlines()
- for moderator in lines:
- if moderator.strip('\n')==nickname:
- return False
- unsuspendAccount(baseDir,nickname)
- handle=nickname+'@'+domain
- removePassword(baseDir,nickname)
- removeTagsForNickname(baseDir,nickname,domain,port)
- if os.path.isdir(baseDir+'/accounts/'+handle):
- shutil.rmtree(baseDir+'/accounts/'+handle)
- if os.path.isfile(baseDir+'/accounts/'+handle+'.json'):
- os.remove(baseDir+'/accounts/'+handle+'.json')
- if os.path.isfile(baseDir+'/wfendpoints/'+handle+'.json'):
- os.remove(baseDir+'/wfendpoints/'+handle+'.json')
- if os.path.isfile(baseDir+'/keys/private/'+handle+'.key'):
- os.remove(baseDir+'/keys/private/'+handle+'.key')
- if os.path.isfile(baseDir+'/keys/public/'+handle+'.pem'):
- os.remove(baseDir+'/keys/public/'+handle+'.pem')
- if os.path.isdir(baseDir+'/sharefiles/'+nickname):
- shutil.rmtree(baseDir+'/sharefiles/'+nickname)
- return True
|