示例#1
0
def outboxUndoFollow(baseDir: str,messageJson: {},debug: bool) -> None:
    """When an unfollow request is received by the outbox from c2s
    This removes the followed handle from the following.txt file
    of the relevant account
    TODO the unfollow should also be sent to the previously followed account
    """
    if not messageJson.get('type'):
        return
    if not messageJson['type']=='Undo':
        return
    if not messageJson.get('object'):
        return
    if not isinstance(messageJson['object'], dict):
        return
    if not messageJson['object'].get('type'):
        return
    if not messageJson['object']['type']=='Follow':
        return
    if not messageJson['object'].get('object'):
        return
    if not messageJson['object'].get('actor'):
        return
    if not isinstance(messageJson['object']['object'], str):
        return
    if debug:
        print('DEBUG: undo follow arrived in outbox')

    nicknameFollower=getNicknameFromActor(messageJson['object']['actor'])
    if not nicknameFollower:
        print('WARN: unable to find nickname in '+messageJson['object']['actor'])
        return
    domainFollower,portFollower=getDomainFromActor(messageJson['object']['actor'])
    domainFollowerFull=domainFollower
    if portFollower:
        if portFollower!=80 and portFollower!=443:
            if ':' not in domainFollower:
                domainFollowerFull=domainFollower+':'+str(portFollower)
    
    nicknameFollowing=getNicknameFromActor(messageJson['object']['object'])
    if not nicknameFollowing:
        print('WARN: unable to find nickname in '+messageJson['object']['object'])
        return
    domainFollowing,portFollowing=getDomainFromActor(messageJson['object']['object'])
    domainFollowingFull=domainFollowing
    if portFollowing:
        if portFollowing!=80 and portFollowing!=443:
            if ':' not in domainFollowing:
                domainFollowingFull=domainFollowing+':'+str(portFollowing)

    if unfollowPerson(baseDir,nicknameFollower,domainFollowerFull, \
                      nicknameFollowing,domainFollowingFull):
        if debug:
            print('DEBUG: '+nicknameFollower+' unfollowed '+nicknameFollowing+'@'+domainFollowingFull)
    else:
        if debug:
            print('WARN: '+nicknameFollower+' could not unfollow '+nicknameFollowing+'@'+domainFollowingFull)
示例#2
0
def outboxSkills(baseDir: str, nickname: str, messageJson: {},
                 debug: bool) -> bool:
    """Handles receiving a skills update
    """
    if not messageJson.get('type'):
        return False
    if not messageJson['type'] == 'Skill':
        return False
    if not messageJson.get('actor'):
        return False
    if not messageJson.get('object'):
        return False
    if not isinstance(messageJson['object'], str):
        return False

    actorNickname = getNicknameFromActor(messageJson['actor'])
    if actorNickname != nickname:
        return False
    domain, port = getDomainFromActor(messageJson['actor'])
    skill = messageJson['object'].replace('"', '').split(';')[0].strip()
    skillLevelPercent = int(messageJson['object'].replace(
        '"', '').split(';')[1].strip())

    return setSkillLevel(baseDir,nickname,domain, \
                         skill,skillLevelPercent)
示例#3
0
def isFollowingActor(baseDir: str,nickname: str,domain: str,actor: str) -> bool:
    """Is the given actor a follower of the given nickname?
    """
    if ':' in domain:
        domain=domain.split(':')[0]
    handle=nickname+'@'+domain
    if not os.path.isdir(baseDir+'/accounts/'+handle):
        return False
    followingFile=baseDir+'/accounts/'+handle+'/following.txt'    
    if not os.path.isfile(followingFile):
        return False
    if actor in open(followingFile).read():
        return True
    followingNickname=getNicknameFromActor(actor)
    if not followingNickname:
        print('WARN: unable to find nickname in '+actor)
        return False
    followingDomain,followingPort=getDomainFromActor(actor)
    followingHandle=followingNickname+'@'+followingDomain
    if followingPort:
        if followingPort!=80 and followingPort!=443:
            if ':' not in followingHandle:
                followingHandle+=':'+str(followingPort)
    if followingHandle in open(followingFile).read():
        return True
    return False
示例#4
0
def undoAnnounce(session,baseDir: str,federationList: [], \
                 nickname: str, domain: str, port: int, \
                 toUrl: str, ccUrl: str, httpPrefix: str, \
                 objectUrl: str, saveToFile: bool, \
                 clientToServer: bool, \
                 sendThreads: [],postLog: [], \
                 personCache: {},cachedWebfingers: {}, \
                 debug: bool) -> {}:
    """Undoes an announce message
    Typically toUrl will be https://www.w3.org/ns/activitystreams#Public
    and ccUrl might be a specific person whose post was repeated and the
    objectUrl is typically the url of the message which was repeated,
    corresponding to url or atomUri in createPostBase
    """
    if not urlPermitted(objectUrl,federationList,"inbox:write"):
        return None

    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)

    newUndoAnnounce = {
        "@context": "https://www.w3.org/ns/activitystreams",
        'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
        'type': 'Undo',
        'cc': [],
        'to': [toUrl],
        'object': {
            'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
            'cc': [],
            'object': objectUrl,
            'to': [toUrl],
            'type': 'Announce'
        }
    }
    if ccUrl:
        if len(ccUrl)>0:
            newUndoAnnounce['object']['cc']=[ccUrl]

    announceNickname=None
    announceDomain=None
    announcePort=None
    if '/users/' in objectUrl or '/profile/' in objectUrl:
        announceNickname=getNicknameFromActor(objectUrl)
        announceDomain,announcePort=getDomainFromActor(objectUrl)

    if announceNickname and announceDomain:
        sendSignedJson(newUndoAnnounce,session,baseDir, \
                       nickname,domain,port, \
                       announceNickname,announceDomain,announcePort, \
                       'https://www.w3.org/ns/activitystreams#Public', \
                       httpPrefix,True,clientToServer,federationList, \
                       sendThreads,postLog,cachedWebfingers,personCache,debug)
            
    return newUndoAnnounce
示例#5
0
def capabilitiesAccept(baseDir: str,httpPrefix: str, \
                       nickname: str,domain: str, port: int, \
                       acceptedActor: str, saveToFile: bool, \
                       acceptedCaps=["inbox:write","objects:read"]) -> {}:
    # This gets returned to capabilities requester
    # This could also be added to a follow Accept activity

    # reject excessively long actors
    if len(acceptedActor)>256:
        return None

    fullDomain=domain
    if port:
        if port!=80 and port !=443:
            if ':' not in domain:
                fullDomain=domain+':'+str(port)
    
    # make directories to store capabilities
    ocapFilename= \
        getOcapFilename(baseDir,nickname,fullDomain,acceptedActor,'accept')
    if not ocapFilename:
        return None
    ocapAccept=None

    # if the capability already exists then load it from file
    if os.path.isfile(ocapFilename):
        with open(ocapFilename, 'r') as fp:
            ocapAccept=commentjson.load(fp)
    # otherwise create a new capability    
    if not ocapAccept:
        acceptedActorNickname=getNicknameFromActor(acceptedActor)
        if not acceptedActorNickname:
            print('WARN: unable to find nickname in '+acceptedActor)
            return None
        acceptedActorDomain,acceptedActorPort=getDomainFromActor(acceptedActor)
        if acceptedActorPort:            
            ocapId=acceptedActorNickname+'@'+acceptedActorDomain+':'+ \
                str(acceptedActorPort)+'#'+createPassword(32)
        else:
            ocapId=acceptedActorNickname+'@'+acceptedActorDomain+'#'+ \
                createPassword(32)
        ocapAccept = {
            "@context": "https://www.w3.org/ns/activitystreams",
            "id": httpPrefix+"://"+fullDomain+"/caps/"+ocapId,
            "type": "Capability",
            "capability": acceptedCaps,
            "scope": acceptedActor,
            "actor": httpPrefix+"://"+fullDomain
        }
        if nickname:
            ocapAccept['actor']=httpPrefix+"://"+fullDomain+'/users/'+nickname

    if saveToFile:
        with open(ocapFilename, 'w') as fp:
            commentjson.dump(ocapAccept, fp, indent=4, sort_keys=False)
    return ocapAccept
示例#6
0
def outboxAnnounce(baseDir: str,messageJson: {},debug: bool) -> bool:
    """ Adds or removes announce entries from the shares collection
    within a given post
    """
    if not messageJson.get('actor'):
        return False
    if not messageJson.get('type'):
        return False
    if not messageJson.get('object'):
        return False
    if messageJson['type']=='Announce':
        if not isinstance(messageJson['object'], str):
            return False
        nickname=getNicknameFromActor(messageJson['actor'])
        if not nickname:
            print('WARN: no nickname found in '+messageJson['actor'])
            return False
        domain,port=getDomainFromActor(messageJson['actor'])
        postFilename=locatePost(baseDir,nickname,domain,messageJson['object'])
        if postFilename:
            updateAnnounceCollection(postFilename,messageJson['actor'],debug)
            return True
    if messageJson['type']=='Undo':
        if not isinstance(messageJson['object'], dict):
            return False
        if not messageJson['object'].get('type'):
            return False
        if messageJson['object']['type']=='Announce':
            if not isinstance(messageJson['object']['object'], str):
                return False
            nickname=getNicknameFromActor(messageJson['actor'])
            if not nickname:
                print('WARN: no nickname found in '+messageJson['actor'])
                return False
            domain,port=getDomainFromActor(messageJson['actor'])
            postFilename=locatePost(baseDir,nickname,domain,messageJson['object']['object'])
            if postFilename:
                undoAnnounceCollectionEntry(postFilename,messageJson['actor'],debug)
                return True
    return False
示例#7
0
def outboxAvailability(baseDir: str,nickname: str,messageJson: {}, \
                       debug: bool) -> bool:
    """Handles receiving an availability update
    """
    if not messageJson.get('type'):
        return False
    if not messageJson['type'] == 'Availability':
        return False
    if not messageJson.get('actor'):
        return False
    if not messageJson.get('object'):
        return False
    if not isinstance(messageJson['object'], str):
        return False

    actorNickname = getNicknameFromActor(messageJson['actor'])
    if actorNickname != nickname:
        return False
    domain, port = getDomainFromActor(messageJson['actor'])
    status = messageJson['object'].replace('"', '')

    return setAvailability(baseDir, nickname, domain, status)
示例#8
0
def receiveAcceptReject(session,baseDir: str, \
                        httpPrefix: str,domain :str,port: int, \
                        sendThreads: [],postLog: [],cachedWebfingers: {}, \
                        personCache: {},messageJson: {},federationList: [], \
                        debug : bool) -> bool:
    """Receives an Accept or Reject within the POST section of HTTPServer
    """
    if messageJson['type'] != 'Accept' and messageJson['type'] != 'Reject':
        return False
    if not messageJson.get('actor'):
        if debug:
            print('DEBUG: ' + messageJson['type'] + ' has no actor')
        return False
    if '/users/' not in messageJson[
            'actor'] and '/profile/' not in messageJson['actor']:
        if debug:
            print('DEBUG: "users" or "profile" missing from actor in ' +
                  messageJson['type'])
        return False
    domain, tempPort = getDomainFromActor(messageJson['actor'])
    if not domainPermitted(domain, federationList):
        if debug:
            print('DEBUG: ' + messageJson['type'] +
                  ' from domain not permitted - ' + domain)
        return False
    nickname = getNicknameFromActor(messageJson['actor'])
    if not nickname:
        if debug:
            print('DEBUG: ' + messageJson['type'] +
                  ' does not contain a nickname')
        return False
    handle = nickname.lower() + '@' + domain.lower()
    # receive follow accept
    acceptFollow(baseDir, domain, messageJson, federationList, debug)
    if debug:
        print('DEBUG: Uh, ' + messageJson['type'] + ', I guess')
    return True
示例#9
0
def acceptFollow(baseDir: str,domain : str,messageJson: {}, \
                 federationList: [],debug : bool) -> None:
    """Receiving a follow Accept activity
    """
    if not messageJson.get('object'):
        return
    if not messageJson['object'].get('type'):
        return
    if not messageJson['object'].get('actor'):
        return
    # no, this isn't a mistake
    if not messageJson['object'].get('object'):
        return
    if not messageJson['object']['type'] == 'Follow':
        return
    if not messageJson.get('to'):
        if debug:
            print('DEBUG: No "to" parameter in follow Accept')
        return
    #if len(messageJson['object']['to'])!=1:
    #    if debug:
    #        print('DEBUG: "to" does not contain a single recipient')
    #        print(str(messageJson['object']['to']))
    #    if messageJson['object'].get('object'):
    #        if not isinstance(messageJson['object']['object'], str):
    #            messageJson['object']['to']=messageJson['object']['object']
    #        else:
    #            return
    #    else:
    #        return
    if debug:
        print('DEBUG: follow Accept received')
    thisActor = messageJson['object']['actor']
    nickname = getNicknameFromActor(thisActor)
    if not nickname:
        print('WARN: no nickname found in ' + thisActor)
        return
    acceptedDomain, acceptedPort = getDomainFromActor(thisActor)
    if not acceptedDomain:
        if debug:
            print('DEBUG: domain not found in ' + thisActor)
        return
    #if acceptedDomain != domain:
    #    if debug:
    #        print('DEBUG: domain mismatch '+acceptedDomain+' != '+domain)
    #    return
    if not nickname:
        if debug:
            print('DEBUG: nickname not found in ' + thisActor)
        return
    if acceptedPort:
        if '/' + acceptedDomain + ':' + str(
                acceptedPort) + '/users/' + nickname not in thisActor:
            if debug:
                print('Port: ' + str(acceptedPort))
                print('Expected: /' + acceptedDomain + ':' +
                      str(acceptedPort) + '/users/' + nickname)
                print('Actual:   ' + thisActor)
                print('DEBUG: unrecognized actor ' + thisActor)
            return
    else:
        if '/' + acceptedDomain + '/users/' + nickname not in thisActor:
            if debug:
                print('Expected: /' + acceptedDomain + '/users/' + nickname)
                print('Actual:   ' + thisActor)
                print('DEBUG: unrecognized actor ' + thisActor)
            return
    followedActor = messageJson['object']['object']
    followedDomain, port = getDomainFromActor(followedActor)
    if not followedDomain:
        return
    followedDomainFull = followedDomain
    if port:
        followedDomainFull = followedDomain + ':' + str(port)
    followedNickname = getNicknameFromActor(followedActor)
    if not followedNickname:
        return

    acceptedDomainFull = acceptedDomain
    if acceptedPort:
        acceptedDomainFull = acceptedDomain + ':' + str(acceptedPort)

    # are capabilities attached? If so then store them
    if messageJson.get('capabilities'):
        if isinstance(messageJson['capabilities'], dict):
            capabilitiesGrantedSave(baseDir, \
                                    nickname,acceptedDomainFull, \
                                    messageJson['capabilities'])

    if followPerson(baseDir, \
                    nickname,acceptedDomainFull, \
                    followedNickname,followedDomainFull, \
                    federationList,debug):
        if debug:
            print('DEBUG: ' + nickname + '@' + acceptedDomainFull +
                  ' followed ' + followedNickname + '@' + followedDomainFull)
    else:
        if debug:
            print('DEBUG: Unable to create follow - ' + nickname + '@' +
                  acceptedDomain + ' -> ' + followedNickname + '@' +
                  followedDomain)
示例#10
0
def createDelete(session,baseDir: str,federationList: [], \
                 nickname: str, domain: str, port: int, \
                 toUrl: str, ccUrl: str, httpPrefix: str, \
                 objectUrl: str,clientToServer: bool, \
                 sendThreads: [],postLog: [], \
                 personCache: {},cachedWebfingers: {}, \
                 debug: bool) -> {}:
    """Creates a delete message
    Typically toUrl will be https://www.w3.org/ns/activitystreams#Public
    and ccUrl might be a specific person whose post is to be deleted
    objectUrl is typically the url of the message, corresponding to url
    or atomUri in createPostBase
    """
    if not urlPermitted(objectUrl, federationList, "inbox:write"):
        return None

    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)

    statusNumber, published = getStatusNumber()
    newDeleteId= \
        httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber
    newDelete = {
        "@context":
        "https://www.w3.org/ns/activitystreams",
        'actor':
        httpPrefix + '://' + fullDomain + '/users/' + nickname,
        'atomUri':
        httpPrefix + '://' + fullDomain + '/users/' + nickname + '/statuses/' +
        statusNumber,
        'cc': [],
        'id':
        newDeleteId + '/activity',
        'object':
        objectUrl,
        'published':
        published,
        'to': [toUrl],
        'type':
        'Delete'
    }
    if ccUrl:
        if len(ccUrl) > 0:
            newDelete['cc'] = [ccUrl]

    deleteNickname = None
    deleteDomain = None
    deletePort = None
    if '/users/' in objectUrl or '/profile/' in objectUrl:
        deleteNickname = getNicknameFromActor(objectUrl)
        deleteDomain, deletePort = getDomainFromActor(objectUrl)

    if deleteNickname and deleteDomain:
        sendSignedJson(newDelete,session,baseDir, \
                       nickname,domain,port, \
                       deleteNickname,deleteDomain,deletePort, \
                       'https://www.w3.org/ns/activitystreams#Public', \
                       httpPrefix,True,clientToServer,federationList, \
                       sendThreads,postLog,cachedWebfingers,personCache,debug)

    return newDelete
示例#11
0
def outboxDelete(baseDir: str,httpPrefix: str, \
                 nickname: str,domain: str, \
                 messageJson: {},debug: bool,
                 allowDeletion: bool) -> None:
    """ When a delete request is received by the outbox from c2s
    """
    if not messageJson.get('type'):
        if debug:
            print('DEBUG: delete - no type')
        return
    if not messageJson['type'] == 'Delete':
        if debug:
            print('DEBUG: not a delete')
        return
    if not messageJson.get('object'):
        if debug:
            print('DEBUG: no object in delete')
        return
    if not isinstance(messageJson['object'], str):
        if debug:
            print('DEBUG: delete object is not string')
        return
    if debug:
        print('DEBUG: c2s delete request arrived in outbox')
    deletePrefix = httpPrefix + '://' + domain
    if not allowDeletion and \
       (not messageJson['object'].startswith(deletePrefix) or \
        not messageJson['actor'].startswith(deletePrefix)):
        if debug:
            print('DEBUG: delete not permitted from other instances')
        return
    messageId = messageJson['object'].replace('/activity', '')
    if '/statuses/' not in messageId:
        if debug:
            print('DEBUG: c2s delete object is not a status')
        return
    if '/users/' not in messageId and '/profile/' not in messageId:
        if debug:
            print('DEBUG: c2s delete object has no nickname')
        return
    deleteNickname = getNicknameFromActor(messageId)
    if deleteNickname != nickname:
        if debug:
            print(
                "DEBUG: you can't delete a post which wasn't created by you (nickname does not match)"
            )
        return
    deleteDomain, deletePort = getDomainFromActor(messageId)
    if ':' in domain:
        domain = domain.split(':')[0]
    if deleteDomain != domain:
        if debug:
            print(
                "DEBUG: you can't delete a post which wasn't created by you (domain does not match)"
            )
        return
    removeModerationPostFromIndex(baseDir, messageId, debug)
    postFilename = locatePost(baseDir, deleteNickname, deleteDomain, messageId)
    if not postFilename:
        if debug:
            print('DEBUG: c2s delete post not found in inbox or outbox')
            print(messageId)
        return True
    deletePost(baseDir, httpPrefix, deleteNickname, deleteDomain, postFilename,
               debug)
    if debug:
        print('DEBUG: post deleted via c2s - ' + postFilename)
示例#12
0
def createAnnounce(session,baseDir: str,federationList: [], \
                   nickname: str, domain: str, port: int, \
                   toUrl: str, ccUrl: str, httpPrefix: str, \
                   objectUrl: str, saveToFile: bool, \
                   clientToServer: bool, \
                   sendThreads: [],postLog: [], \
                   personCache: {},cachedWebfingers: {}, \
                   debug: bool,projectVersion: str) -> {}:
    """Creates an announce message
    Typically toUrl will be https://www.w3.org/ns/activitystreams#Public
    and ccUrl might be a specific person favorited or repeated and the
    followers url objectUrl is typically the url of the message,
    corresponding to url or atomUri in createPostBase
    """
    if not urlPermitted(objectUrl,federationList,"inbox:write"):
        return None

    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)

    statusNumber,published = getStatusNumber()
    newAnnounceId= \
        httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber
    newAnnounce = {
        "@context": "https://www.w3.org/ns/activitystreams",
        'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
        'atomUri': httpPrefix+'://'+fullDomain+'/users/'+nickname+'/statuses/'+statusNumber,
        'cc': [],
        'id': newAnnounceId+'/activity',
        'object': objectUrl,
        'published': published,
        'to': [toUrl],
        'type': 'Announce'
    }
    if ccUrl:
        if len(ccUrl)>0:
            newAnnounce['cc']=[ccUrl]
    if saveToFile:
        outboxDir = createOutboxDir(nickname,domain,baseDir)
        filename=outboxDir+'/'+newAnnounceId.replace('/','#')+'.json'
        with open(filename, 'w') as fp:
            commentjson.dump(newAnnounce, fp, indent=4, sort_keys=False)

    announceNickname=None
    announceDomain=None
    announcePort=None
    if '/users/' in objectUrl or '/profile/' in objectUrl:
        announceNickname=getNicknameFromActor(objectUrl)
        announceDomain,announcePort=getDomainFromActor(objectUrl)

    if announceNickname and announceDomain:
        sendSignedJson(newAnnounce,session,baseDir, \
                       nickname,domain,port, \
                       announceNickname,announceDomain,announcePort, \
                       'https://www.w3.org/ns/activitystreams#Public', \
                       httpPrefix,True,clientToServer,federationList, \
                       sendThreads,postLog,cachedWebfingers,personCache, \
                       debug,projectVersion)
            
    return newAnnounce
示例#13
0
def outboxDelegate(baseDir: str, authenticatedNickname: str, messageJson: {},
                   debug: bool) -> bool:
    """Handles receiving a delegation request
    """
    if not messageJson.get('type'):
        return False
    if not messageJson['type'] == 'Delegate':
        return False
    if not messageJson.get('object'):
        return False
    if not isinstance(messageJson['object'], dict):
        return False
    if not messageJson['object'].get('type'):
        return False
    if not messageJson['object']['type'] == 'Role':
        return False
    if not messageJson['object'].get('object'):
        return False
    if not messageJson['object'].get('actor'):
        return False
    if not isinstance(messageJson['object']['object'], str):
        return False
    if ';' not in messageJson['object']['object']:
        print('WARN: No ; separator between project and role')
        return False

    delegatorNickname = getNicknameFromActor(messageJson['actor'])
    if delegatorNickname != authenticatedNickname:
        return
    domain, port = getDomainFromActor(messageJson['actor'])
    project = messageJson['object']['object'].split(';')[0].strip()

    # instance delegators can delagate to other projects
    # than their own
    canDelegate = False
    delegatorRoles=getRoles(baseDir,delegatorNickname, \
                            domain,'instance')
    if delegatorRoles:
        if 'delegator' in delegatorRoles:
            canDelegate = True

    if canDelegate == False:
        canDelegate = True
        # non-instance delegators can only delegate within their project
        delegatorRoles=getRoles(baseDir,delegatorNickname, \
                                domain,project)
        if delegatorRoles:
            if 'delegator' not in delegatorRoles:
                return False
        else:
            return False

    if canDelegate == False:
        return False
    nickname = getNicknameFromActor(messageJson['object']['actor'])
    if not nickname:
        print('WARN: unable to find nickname in ' +
              messageJson['object']['actor'])
        return False
    domainFull = domain
    if port:
        if port != 80 and port != 443:
            if ':' not in domain:
                domainFull = domain + ':' + str(port)
    role = messageJson['object']['object'].split(';')[1].strip().lower()

    if not role:
        setRole(baseDir, nickname, domain, project, None)
        return True

    # what roles is this person already assigned to?
    existingRoles = getRoles(baseDir, nickname, domain, project)
    if existingRoles:
        if role in existingRoles:
            if debug:
                print(nickname + '@' + domain +
                      ' is already assigned to the role ' + role +
                      ' within the project ' + project)
            return False
    setRole(baseDir, nickname, domain, project, role)
    if debug:
        print(nickname + '@' + domain + ' assigned to the role ' + role +
              ' within the project ' + project)
    return True
示例#14
0
def capabilitiesUpdate(baseDir: str,httpPrefix: str, \
                       nickname: str,domain: str, port: int, \
                       updateActor: str, \
                       updateCaps: []) -> {}:
    """Used to sends an update for a change of object capabilities
    Note that the capability id gets changed with a new random token
    so that the old capabilities can't continue to be used
    """

    # reject excessively long actors
    if len(updateActor)>256:
        return None

    fullDomain=domain
    if port:
        if port!=80 and port !=443:
            if ':' not in domain:
                fullDomain=domain+':'+str(port)
    
    # Get the filename of the capability
    ocapFilename= \
        getOcapFilename(baseDir,nickname,fullDomain,updateActor,'accept')
    if not ocapFilename:
        return None

    # The capability should already exist for it to be updated
    if not os.path.isfile(ocapFilename):
        return None

    # create an update activity
    ocapUpdate = {
        "@context": "https://www.w3.org/ns/activitystreams",
        'type': 'Update',
        'actor': httpPrefix+'://'+fullDomain+'/users/'+nickname,
        'to': [updateActor],
        'cc': [],
        'object': {}
    }

    # read the existing capability
    with open(ocapFilename, 'r') as fp:
        ocapJson=commentjson.load(fp)

    # set the new capabilities list. eg. ["inbox:write","objects:read"]
    ocapJson['capability']=updateCaps

    # change the id, so that the old capabilities can't continue to be used
    updateActorNickname=getNicknameFromActor(updateActor)
    if not updateActorNickname:
        print('WARN: unable to find nickname in '+updateActor)
        return None
    updateActorDomain,updateActorPort=getDomainFromActor(updateActor)
    if updateActorPort:
        ocapId=updateActorNickname+'@'+updateActorDomain+':'+ \
            str(updateActorPort)+'#'+createPassword(32)
    else:
        ocapId=updateActorNickname+'@'+updateActorDomain+'#'+createPassword(32)
    ocapJson['id']=httpPrefix+"://"+fullDomain+"/caps/"+ocapId
    ocapUpdate['object']=ocapJson

    # save it again
    with open(ocapFilename, 'w') as fp:
        commentjson.dump(ocapJson, fp, indent=4, sort_keys=False)
    
    return ocapUpdate
示例#15
0
def getFollowersOfActor(baseDir :str,actor :str,debug: bool) -> {}:
    """In a shared inbox if we receive a post we know who it's from
    and if it's addressed to followers then we need to get a list of those.
    This returns a list of account handles which follow the given actor
    and also the corresponding capability id if it exists
    """
    if debug:
        print('DEBUG: getting followers of '+actor)
    recipientsDict={}
    if ':' not in actor:
        return recipientsDict
    httpPrefix=actor.split(':')[0]
    nickname=getNicknameFromActor(actor)
    if not nickname:
        if debug:
            print('DEBUG: no nickname found in '+actor)
        return recipientsDict
    domain,port=getDomainFromActor(actor)
    if not domain:
        if debug:
            print('DEBUG: no domain found in '+actor)
        return recipientsDict
    actorHandle=nickname+'@'+domain
    if debug:
        print('DEBUG: searching for handle '+actorHandle)
    # for each of the accounts
    for subdir, dirs, files in os.walk(baseDir+'/accounts'):
        for account in dirs:
            if '@' in account and not account.startswith('inbox@'):
                followingFilename = os.path.join(subdir, account)+'/following.txt'
                if debug:
                    print('DEBUG: examining follows of '+account)
                    print(followingFilename)
                if os.path.isfile(followingFilename):
                    # does this account follow the given actor?
                    if debug:
                        print('DEBUG: checking if '+actorHandle+' in '+followingFilename)
                    if actorHandle in open(followingFilename).read():
                        if debug:
                            print('DEBUG: '+account+' follows '+actorHandle)
                        ocapFilename=baseDir+'/accounts/'+account+'/ocap/accept/'+httpPrefix+':##'+domain+':'+str(port)+'#users#'+nickname+'.json'
                        if debug:
                            print('DEBUG: checking capabilities of'+account)
                        if os.path.isfile(ocapFilename):                        
                            with open(ocapFilename, 'r') as fp:
                                ocapJson=commentjson.load(fp)
                                if ocapJson.get('id'):
                                    if debug:
                                        print('DEBUG: capabilities id found for '+account)
                
                                    recipientsDict[account]=ocapJson['id']
                                else:
                                    if debug:
                                        print('DEBUG: capabilities has no id attribute')
                                    recipientsDict[account]=None
                        else:
                            if debug:
                                print('DEBUG: No capabilities file found for '+account+' granted by '+actorHandle)
                                print(ocapFilename)
                            recipientsDict[account]=None
    return recipientsDict
示例#16
0
def receiveFollowRequest(session,baseDir: str,httpPrefix: str, \
                         port: int,sendThreads: [],postLog: [], \
                         cachedWebfingers: {},personCache: {}, \
                         messageJson: {},federationList: [], \
                         debug : bool,projectVersion: str, \
                         acceptedCaps=["inbox:write","objects:read"]) -> bool:
    """Receives a follow request within the POST section of HTTPServer
    """
    if not messageJson['type'].startswith('Follow'):
        return False
    print('Receiving follow request')
    if not messageJson.get('actor'):
        if debug:
            print('DEBUG: follow request has no actor')
        return False
    if '/users/' not in messageJson['actor'] and '/profile/' not in messageJson['actor']:
        if debug:
            print('DEBUG: "users" or "profile" missing from actor')            
        return False
    domain,tempPort=getDomainFromActor(messageJson['actor'])
    fromPort=port
    domainFull=domain
    if tempPort:
        fromPort=tempPort
        if tempPort!=80 and tempPort!=443:
            if ':' not in domain:
                domainFull=domain+':'+str(tempPort)
    if not domainPermitted(domain,federationList):
        if debug:
            print('DEBUG: follower from domain not permitted - '+domain)
        return False
    nickname=getNicknameFromActor(messageJson['actor'])
    if not nickname:
        if debug:
            print('DEBUG: follow request does not contain a nickname')
        return False
    if not messageJson.get('to'):
        messageJson['to']=messageJson['object']
    handle=nickname.lower()+'@'+domain.lower()
    if '/users/' not in messageJson['object'] and '/profile/' not in messageJson['object']:
        if debug:
            print('DEBUG: "users" or "profile" not found within object')
        return False
    domainToFollow,tempPort=getDomainFromActor(messageJson['object'])
    if not domainPermitted(domainToFollow,federationList):
        if debug:
            print('DEBUG: follow domain not permitted '+domainToFollow)
        return True
    domainToFollowFull=domainToFollow
    if tempPort:
        if tempPort!=80 and tempPort!=443:
            if ':' not in domainToFollow:
                domainToFollowFull=domainToFollow+':'+str(tempPort)            
    nicknameToFollow=getNicknameFromActor(messageJson['object'])
    if not nicknameToFollow:
        if debug:
            print('DEBUG: follow request does not contain a nickname for the account followed')
        return True
    handleToFollow=nicknameToFollow+'@'+domainToFollow
    if domainToFollow==domain:
        if not os.path.isdir(baseDir+'/accounts/'+handleToFollow):
            if debug:
                print('DEBUG: followed account not found - '+ \
                      baseDir+'/accounts/'+handleToFollow)
            return True
        
    if isFollowerOfPerson(baseDir, \
                          nicknameToFollow,domainToFollowFull, \
                          nickname,domainFull):
        if debug:
            print('DEBUG: '+nickname+'@'+domain+ \
                  ' is already a follower of '+ \
                  nicknameToFollow+'@'+domainToFollow)
        return True
    
    # what is the followers policy?
    if followApprovalRequired(baseDir,nicknameToFollow, \
                              domainToFollow,debug):
        rejectedFollowsFilename=baseDir+'/accounts/'+nickname+'@'+domain+'/followrejects.txt'
        if os.path.isfile(rejectedFollowsFilename):
            denyHandle=nicknameToFollow+'@'+domainToFollowFull
            if denyHandle in open(rejectedFollowsFilename).read():
                print(denyHandle+' was already denied as a follower of '+nickname)
                return True

        print('Storing follow request for approval')
        return storeFollowRequest(baseDir, \
                                  nicknameToFollow,domainToFollow,port, \
                                  nickname,domain,fromPort,
                                  messageJson,debug)
    else:
        print('Follow request does not require approval')
        # update the followers
        if os.path.isdir(baseDir+'/accounts/'+nicknameToFollow+'@'+domainToFollow):
            followersFilename=baseDir+'/accounts/'+nicknameToFollow+'@'+domainToFollow+'/followers.txt'
            approveHandle=nickname+'@'+domain
            if fromPort:
                approveHandle=approveHandle+':'+str(fromPort)
            print('Updating followers file: '+followersFilename+' adding '+approveHandle)
            if os.path.isfile(followersFilename):
                if approveHandle not in open(followersFilename).read():
                    followersFile=open(followersFilename, "a+")
                    followersFile.write(approveHandle+'\n')
                    followersFile.close()
            else:
                followersFile=open(followersFilename, "w+")
                followersFile.write(approveHandle+'\n')
                followersFile.close()

    print('Beginning follow accept')
    return followedAccountAccepts(session,baseDir,httpPrefix, \
                                  nicknameToFollow,domainToFollow,port, \
                                  nickname,domain,fromPort, \
                                  messageJson['actor'],federationList,
                                  messageJson,acceptedCaps, \
                                  sendThreads,postLog, \
                                  cachedWebfingers,personCache, \
                                  debug,projectVersion)