コード例 #1
0
ファイル: restInterface.py プロジェクト: Magnil/fairywren
	def login(self,env,start_response):
		
		cl = vanilla.getContentLength(env)
		if cl == None:
			return vanilla.http_error(411,env,start_response,'missing Content-Length header')
		
		content = env['wsgi.input'].read(cl)
		query = urlparse.parse_qs(content)
		
		if 'username' not in query:
			return vanilla.http_error(400,env,start_response,msg='missing username')
		#Use first occurence from query string
		username = query['username'][0]
		
		if 'password' not in query:
			return vanilla.http_error(400,env,start_response,msg='missing password')
		#Use first occurence from query string
		password = query['password'][0]
		
		userId = self.authenticateUser(username,password)
		if userId == None:
			self.logger.info('Failed authorization for user:%s' , username)
			return vanilla.sendJsonWsgiResponse(env,start_response,{'error':'bad username or password'})
		
		session = self.sm.startSession(username,userId)
			
		return vanilla.sendJsonWsgiResponse(env,start_response,self.getResponseForSession(session),additionalHeaders=[session.getCookie()])
コード例 #2
0
ファイル: webapi.py プロジェクト: yangs1202/fairywren
	def claimInvite(self,env,start_response,secret,username,password):
		secret = base64.urlsafe_b64decode(secret + '=')
		
		try:
			newuser = self.users.claimInvite(secret,username,password)
		except users.UserAlreadyExists:
			return vanilla.http_error(409,env,start_response,msg='User with that name already exists')
		except ValueError as e:
			return vanilla.http_error(404,env,start_response,msg=e.message)
		
		return vanilla.sendJsonWsgiResponse(env,start_response,{'href' : newuser})
コード例 #3
0
ファイル: webapi.py プロジェクト: yangs1202/fairywren
	def changePassword(self,env,start_response,session,uid,password):
		uid = int(uid,16)
		
		if None == self.authmgr.changePassword(uid,password):
			return vanilla.http_error(400,env,start_response)
		
		return vanilla.sendJsonWsgiResponse(env,start_response,{})
コード例 #4
0
ファイル: webapi.py プロジェクト: yangs1202/fairywren
		def authenticateUser(username,password):	
			#Password comes across as 64 bytes of base64 encoded data
			#with trailing ='s lopped off. 
			password += '=='
			
			if len(password) != 88: #64 bytes in base64 is length 88
				return None
				return vanilla.http_error(400,env,start_response,msg='password too short')
			
			try:
				password = base64.urlsafe_b64decode(password)
			except TypeError:
				return None
				return vanilla.http_error(400,env,start_response,msg='password poorly formed')
			
			return authmgr.authenticateUser(username,password)
コード例 #5
0
ファイル: webapi.py プロジェクト: yangs1202/fairywren
	def changeRolesOfUser(self,env,start_response,session,uid,roles):
		uid = int(uid,16)
		try:
			self.users.setUserRoles(roles,uid)
		except ValueError as e:
			return vanilla.http_error(400,env,start_response,msg=e.message)
		return vanilla.sendJsonWsgiResponse(env,start_response,{'roles':self.users.getUserRoles(uid)})
コード例 #6
0
    def __call__(self, env, start_response):
        #Extract and normalize the path
        #Posix path may not be the best approach here but
        #no alternate has been found
        pathInfo = posixpath.normpath(env['PATH_INFO'])

        #Split the path into components. Drop the first
        #since it should always be the empty string
        pathComponents = pathInfo.split('/')[1 + self.pathDepth:]

        env['fairywren.pathComponents'] = pathComponents

        requestMethod = env['REQUEST_METHOD']

        #The default is request not found
        errorCode = 404

        #Find a resource with a patch matching the requested one
        for resource in self.resources:
            kwargs = resource.wants(pathComponents)

            if kwargs == None:
                continue

            #If the method does not agree with the resource, the
            #code is method not supported
            if requestMethod != resource.method:
                errorCode = 405
                continue

            self.logger.debug('%s:%s handled by %s', requestMethod, pathInfo,
                              resource.getName())
            if resource.requireAuthentication:
                session = self.sm.getSession(env)

                if session == None:
                    return vanilla.sendJsonWsgiResponse(
                        env, start_response, restInterface.NOT_AUTHENTICATED)

                #Check to see if the resource requires authorization
                if resource.requireAuthorization:
                    authorized = resource.allowSelf and resource.getOwnerId(
                        *pathComponents) == session.getId()
                    authorized |= self.authorizeUser(session,
                                                     resource.allowedRoles)
                    if not authorized:
                        self.logger.debug('%s:%s not authorized for %s',
                                          requestMethod, pathInfo,
                                          session.getUsername())
                        return vanilla.sendJsonWsgiResponse(
                            env, start_response, restInterface.NOT_AUTHORIZED)

                return resource(env, start_response, session, **kwargs)
            else:
                return resource(env, start_response, **kwargs)

        self.logger.info('%s:%s not handled, %d', requestMethod, pathInfo,
                         errorCode)
        return vanilla.http_error(errorCode, env, start_response)
コード例 #7
0
ファイル: webapi.py プロジェクト: yangs1202/fairywren
	def inviteStatus(self,env,start_response,secret):
		secret = base64.urlsafe_b64decode(secret + '=')
		try:
			claimed =self.users.getInviteState(secret)
		except ValueError as e:
			return vanilla.http_error(404,env,start_response,msg=e.message)
		
		return vanilla.sendJsonWsgiResponse(env,start_response,{'claimed':claimed})
コード例 #8
0
ファイル: webapi.py プロジェクト: yangs1202/fairywren
	def updateTorrent(self,env,start_response,session,uid,extended,title):
		uid = int(uid,16)
		
		try:
			self.torrents.updateTorrent(uid,title,extended)
		except ValueError as e:
			return vanilla.http_error(404,env,start_response,msg=e.message)			
			
		return vanilla.sendJsonWsgiResponse(env,start_response,{})
コード例 #9
0
ファイル: webapi.py プロジェクト: yangs1202/fairywren
	def deleteTorrent(self,env,start_response,session,uid):
		uid = int(uid,16)
		
		try:
			torrent = self.torrents.deleteTorrent(uid)
		except ValueError as e:
			return vanilla.http_error(404,env,start_response,msg=e.message)
			
		return vanilla.sendJsonWsgiResponse(env,start_response,{})
コード例 #10
0
ファイル: webapi.py プロジェクト: yangs1202/fairywren
	def addUser(self,env,start_response,session,password,username):
		
		try:
			resourceForNewUser,_ = self.users.addUser(username,password)
		except users.UserAlreadyExists:		
			return vanilla.http_error(409,env,start_response,'user already exists')
		
		response = { 'href' : resourceForNewUser } 
		return vanilla.sendJsonWsgiResponse(env,start_response,response)
コード例 #11
0
ファイル: webapi.py プロジェクト: yangs1202/fairywren
	def searchTorrents(self,env,start_response,session,query):
		tokens = query.get('token')
		if tokens == None:
			return vanilla.http_error(400,env,start_response,'search must have at least one instance of token parameter')
			
		if len(tokens) > 5:
			return vanilla.http_error(400,env,start_response,'search may not have more than 5 tokens')
			
		listOfTorrents = []
		for torrent in self.torrents.searchTorrents(tokens):
			torrentInfoHash = torrent.pop('infoHash')
			torrent.pop('id')
			seeds, leeches = self.peers.getNumberOfPeers(torrentInfoHash)
			torrent['seeds'] = seeds
			torrent['leeches'] = leeches
			listOfTorrents.append(torrent)
			
		return vanilla.sendJsonWsgiResponse(env,start_response,
		{'torrents': listOfTorrents})
コード例 #12
0
ファイル: webapi.py プロジェクト: yangs1202/fairywren
	def userInfo(self,env,start_response,session,uid):
		uid = int(uid,16)
		
		response = self.users.getInfo(uid)
		
		if response == None:
			return vanilla.http_error(404,env,start_response)
			
		if session.getId() == uid:
			response['announce'] = { 'href': self.torrents.getAnnounceUrlForUser(uid) }
				
		return vanilla.sendJsonWsgiResponse(env,start_response,response)
コード例 #13
0
ファイル: webapi.py プロジェクト: yangs1202/fairywren
	def torrentInfo(self,env,start_response,session,uid):
		uid = int(uid,16)
		response = self.torrents.getInfo(uid)
		if response == None:
			return vanilla.http_error(404,env,start_response,msg='no such torrent')

		torrentInfoHash = response.pop('infoHash')
		numSeeds,numLeeches = self.peers.getNumberOfPeers(torrentInfoHash)
		response['extended'] = self.torrents.getExtendedInfo(uid)
		response['seeds'] = numSeeds
		response['leeches'] = numLeeches
		return vanilla.sendJsonWsgiResponse(env,start_response,response)
コード例 #14
0
ファイル: webapi.py プロジェクト: yangs1202/fairywren
	def listTorrents(self,env,start_response,session):
		
		if 'QUERY_STRING' not in env:
			query = {}
		else:
			query = urlparse.parse_qs(env['QUERY_STRING'])
			
		if 'search' in query:
			return self.searchTorrents(env,start_response,session,query)
		
		#Use the first occurence of the supplied parameter
		#With a default 
		resultSize = query.get('resultSize',[self.MAX_TORRENTS_PER_RESULT])[0]
		
		try:
			resultSize = int(resultSize)
		except ValueError:
			return vanilla.http_error(400,env,start_response,'resultSize must be integer')
		
		#Use the first occurence of the supplied parameter
		#With a default of zero	
		subset = query.get('subset',[0])[0]
		
		try:
			subset = int(subset)
		except ValueError:
			return vanilla.http_error(400,env,start_response,'subset must be integer')

		listOfTorrents = []
		
		for torrent in self.torrents.getTorrents(resultSize,subset):
			torrent.pop('id')
			torrentInfoHash = torrent.pop('infoHash')
			seeds, leeches = self.peers.getNumberOfPeers(torrentInfoHash)
			torrent['seeds'] = seeds
			torrent['leeches'] = leeches
			listOfTorrents.append(torrent)

		return vanilla.sendJsonWsgiResponse(env,start_response,
		{'torrents' : listOfTorrents ,'numSubsets' : int(math.ceil(self.torrents.getNumTorrents() / float(resultSize)))} )
コード例 #15
0
    def login(self, env, start_response):

        cl = vanilla.getContentLength(env)
        if cl == None:
            return vanilla.http_error(411, env, start_response,
                                      'missing Content-Length header')

        content = env['wsgi.input'].read(cl)
        query = urlparse.parse_qs(content)

        if 'username' not in query:
            return vanilla.http_error(400,
                                      env,
                                      start_response,
                                      msg='missing username')
        #Use first occurence from query string
        username = query['username'][0]

        if 'password' not in query:
            return vanilla.http_error(400,
                                      env,
                                      start_response,
                                      msg='missing password')
        #Use first occurence from query string
        password = query['password'][0]

        userId = self.authenticateUser(username, password)
        if userId == None:
            self.logger.info('Failed authorization for user:%s', username)
            return vanilla.sendJsonWsgiResponse(
                env, start_response, {'error': 'bad username or password'})

        session = self.sm.startSession(username, userId)

        return vanilla.sendJsonWsgiResponse(
            env,
            start_response,
            self.getResponseForSession(session),
            additionalHeaders=[session.getCookie()])
コード例 #16
0
ファイル: restInterface.py プロジェクト: Magnil/fairywren
	def __call__(self,env,start_response):		
		#Extract and normalize the path
		#Posix path may not be the best approach here but 
		#no alternate has been found
		pathInfo = posixpath.normpath(env['PATH_INFO'])
		
		#Split the path into components. Drop the first
		#since it should always be the empty string
		pathComponents = pathInfo.split('/')[1+self.pathDepth:]
		
		env['fairywren.pathComponents'] = pathComponents
		
		requestMethod = env['REQUEST_METHOD']
		
		#The default is request not found
		errorCode = 404
		
		#Find a resource with a patch matching the requested one
		for resource in self.resources:	
			kwargs = resource.wants(pathComponents)

			if kwargs == None:
				continue

			#If the method does not agree with the resource, the
			#code is method not supported
			if requestMethod != resource.method:
				errorCode = 405	
				continue							
			
			self.logger.debug('%s:%s handled by %s',requestMethod,pathInfo,resource.getName())
			if resource.requireAuthentication:
				session = self.sm.getSession(env)

				if session == None:
					return vanilla.sendJsonWsgiResponse(env,start_response,restInterface.NOT_AUTHENTICATED)
				
				#Check to see if the resource requires authorization
				if resource.requireAuthorization:
					authorized = resource.allowSelf and resource.getOwnerId(*pathComponents)==session.getId()
					authorized |= self.authorizeUser(session,resource.allowedRoles)
					if not authorized:
						self.logger.debug('%s:%s not authorized for %s',requestMethod,pathInfo,session.getUsername())
						return vanilla.sendJsonWsgiResponse(env,start_response,restInterface.NOT_AUTHORIZED)

				return resource(env,start_response,session,**kwargs)
			else:
				return resource(env,start_response,**kwargs)
				
		self.logger.info('%s:%s not handled, %d', requestMethod,pathInfo,errorCode)
		return vanilla.http_error(errorCode,env,start_response)
コード例 #17
0
ファイル: webapi.py プロジェクト: yangs1202/fairywren
	def downloadTorrent(self,env,start_response,session,uid):
		uid = int(uid,16)
		
		try:
			torrent = self.torrents.getTorrentForDownload(uid,session.getId())
		except ValueError as e:
			return vanilla.http_error(404,env,start_response,msg=e.message)
		
		headers = [('Content-Type','application/x-bittorrent')]
		headers.append(('Content-Disposition','attachment; filename="%s.torrent"' % vanilla.sanitizeForContentDispositionHeaderFilename(torrent.getTitle()) ))
		headers.append(('Cache-Control','no-cache'))

		start_response('200 OK',headers)
		
		return [torrent.raw()]
コード例 #18
0
ファイル: webapi.py プロジェクト: yangs1202/fairywren
	def createTorrent(self,env,start_response,session):
		
		if not 'CONTENT_TYPE' in env:
			return vanilla.http_error(411,env,start_response,'missing Content-Type header')
		
		contentType = env['CONTENT_TYPE']
			
		if 'multipart/form-data' not in contentType:
			return vanilla.http_error(415,env,start_response,'must be form upload')
		
		forms,files = multipart.parse_form_data(env)
		
		if 'torrent' not in files or 'title' not in forms:
			return vanilla.http_error(400,env,start_response,'missing torrent or title')
		
		try:
			extended = json.loads(forms.get('extended','{}'))
		except ValueError:
			return vanilla.http_error(400,env,start_response,'bad extended info')
			
		if not isinstance(extended,dict):
			return vanilla.http_error(400,env,start_response,'extended info must be dict')
		
		data = files['torrent'].raw
		try:
			newTorrent = torrents.Torrent.fromBencodedData(data)
		except ValueError as e:
			return vanilla.http_error(400,env,start_response,str(e))
		
		response = {}
		response['redownload'] = newTorrent.scrub()
		response['redownload'] |= self.torrents.getAnnounceUrlForUser(session.getId())!=newTorrent.getAnnounceUrl()
		
		try:	
			url,infoUrl = self.torrents.addTorrent(newTorrent,forms['title'],session.getId(),extended)
		except ValueError as e: #Thrown when a torrent already exists with this info hash
			return vanilla.http_error(400,env,start_response,e.message)
			
		response['metainfo'] = { 'href' : url }
		response['info'] = { 'href' : infoUrl }
			
		return vanilla.sendJsonWsgiResponse(env,start_response,response)
コード例 #19
0
	def announce(self,env,start_response):
		#Extract and normalize the path
		#Posix path may not be the best approach here but 
		#no alternate has been found
		pathInfo = posixpath.normpath(env['PATH_INFO'])
		
		#Split the path into components. Drop the first
		#since it should always be the empty string
		pathComponents = pathInfo.split('/')[1+self.pathDepth:]
		
		#A SHA512 encoded in base64 is 88 characters
		#but the last two are always '==' so 
		#86 is used here
		if len(pathComponents) !=2 or len(pathComponents[0]) != 86 or pathComponents[1] != 'announce':
			return vanilla.http_error(404,env,start_response)
					
		#Only GET requests are valid
		if env['REQUEST_METHOD'] != 'GET':
			return vanilla.http_error(405,env,start_response)
		
		#Add the omitted equals signs back in
		secretKey = pathComponents[0] + '=='
		
		#base64 decode the secret key
		try:
			secretKey = base64.urlsafe_b64decode(secretKey)
		except TypeError:
			return vanilla.http_error(404,env,start_response)
		
		#Extract the IP of the peer
		peerIp = getClientAddress(env)
		peerIpAsString = peerIp
		try:
			peerIp = dottedQuadToInt(peerIp)
		except ValueError:
			return vanilla.http_error(500,env,start_response)
						
		#Parse the query string. Absence indicates error
		if 'QUERY_STRING' not in env:
			return vanilla.http_error(400,env,start_response)
			
		query = urlparse.parse_qs(env['QUERY_STRING'])
		
		#List of tuples. Each tuple is
		#
		#Parameter name
		#default value (if any)
		#type conversion, side-effect free callable
		params = []
		
		def validateInfoHash(info_hash):
			#Info hashes are a SHA1 hash, and are always 20 bytes
			if len(info_hash) != 20:
				raise ValueError("Length " + str(len(info_hash)) + ' not acceptable')
			return info_hash
			
		params.append(('info_hash',None,validateInfoHash))
		
		def validatePeerId(peer_id):
			#Peer IDs are a string chosen by the peer to identify itself
			#and are always 20 bytes
			if len(peer_id) != 20:
				raise ValueError("Improper Length")
			return peer_id
			
		params.append(('peer_id',None,validatePeerId))
		
		def validatePort(port):
			port = int(port)
			#Ipv4 ports should not be higher than this value
			if port > 2 ** 16 - 1 or port <= 0:
				raise ValueError("Port outside of range")
			return port
			
		def validateByteCount(byteCount):
			byteCount = int(byteCount)
				
			if byteCount < 0:
				raise ValueError('byte count cannot be negative')
			return byteCount
			
		params.append(('port',None,validatePort))
		params.append(('uploaded',None,validateByteCount))
		params.append(('downloaded',None,validateByteCount))
		params.append(('left',None,validateByteCount))
		#If the client doesn't specify the compact parameter, it is
		#safe to assume that compact responses are understood. So a
		#default value of 1 is used. Additionally, any non zero
		#value provided assumes the client wants a compact response
		params.append(('compact',1,int))
		
		def validateEvent(event):
			event = event.lower()
			if event not in ['started','stopped','completed']:
				raise ValueError("Unknown event")
			return event
		
		params.append(('event','update',validateEvent))
		
		maxNumWant = 35
		def limitNumWant(numwant):
			numwant = int(numwant)
			
			if numwant < 0:
				raise ValueError('numwant cannot be negative')
			
			numwant = min(numwant,maxNumWant)
			return numwant
			
		params.append(('numwant',maxNumWant,limitNumWant))
		
		#Dictionary holding parameters to query
		p = dict()
		#Use the params to generate the parameters
		for param,defaultValue,typeConversion in params:
			#If the parameter is in the query, extract the first
			#occurence and type convert if requested
			if param in query:
				p[param] = query[param][0]
				
				if typeConversion:
					try:
						p[param] = typeConversion(p[param])
					except ValueError as e:
						return vanilla.http_error(400,env,start_response,msg='bad value for ' + param)

			#If the parameter is not in the query, then 
			#use a default value is present. Otherwise this is an error
			else:
				if defaultValue == None:
					return vanilla.http_error(400,env,start_response,msg='missing ' + param)
				p[param] = defaultValue
				
				
		#Make sure the secret key is valid
		userId = self.auth.authenticateSecretKey(secretKey)
		if userId == None:
			response = {}
			response['failure reason'] = 'failed to authenticate secret key'
			return sendBencodedWsgiResponse(env,start_response,response)
			
			
		#Make sure the info hash is allowed
		torrentId = self.auth.authorizeInfoHash(p['info_hash'])
		if torrentId == None:
			response = {}
			response['failure reason'] = 'unauthorized info hash'
			return sendBencodedWsgiResponse(env,start_response,response)
		
		
		#Construct the peers entry
		peer = peers.Peer(peerIp,p['port'],p['left'])
		
		#This is the basic response format
		response = {}
		response['interval'] = 5*60
		response['complete'] = 0
		response['incomplete'] = 0
		response['peers'] = []
		
		#This value is set to True if the number of seeds or leeches
		#changes in the course of processing this result
		change = False
		
		#This value is set to true if the peer is added, false if removed
		addPeer = False
		
		#For all 3 cases here just return peers
		if p['event'] in ['started','completed','update']:
			response['complete'] = self.peers.getNumberOfLeeches(p['info_hash'])
			response['incomplete'] = self.peers.getNumberOfSeeds(p['info_hash'])
			
			change = self.peers.updatePeer(p['info_hash'],peer)
			
			if change:
				addPeer = True
			
			peersForResponse = self.peers.getPeers(p['info_hash'])
			
			#Return a compact response or a traditional response
			#based on what is requested
			if p['compact'] != 0:
				peerStruct = struct.Struct('!IH')
				maxSize = p['numwant'] * peerStruct.size
				peersBuffer = array.array('c')
				
				for peer in itertools.islice(peersForResponse,0,p['numwant']):
					peersBuffer.fromstring(peerStruct.pack(peer.ip,peer.port))
                    
				response['peers'] = peersBuffer.tostring()
			else:
				for peer in itertools.islice(peersForResponse,0,p['numwant']):
					#For non-compact responses, use a bogus peerId. Hardly any client
					#uses this type of response anyways. There is no real meaning to the
					#peer ID except informal agreements.
					response['peers'].append({'peer id':'0'*20,'ip':socket.inet_ntoa(struct.pack('!I',peer.ip)),'port':peer.port})
		#For stop event, just remove the peer. Don't return anything	
		elif p['event'] == 'stopped':
			change = self.peers.removePeer(p['info_hash'],peer)
			addPeer = False
			
		#Log the successful announce
		self.announceLog.info('%s:%d %s,%s,%d',peerIpAsString,p['port'],p['info_hash'].encode('hex').upper(),p['event'],p['left'])
		
		for callback in self.afterAnnounce:
			callback(userId,p['info_hash'],peerIpAsString,p['port'],p['peer_id'])
			
		return sendBencodedWsgiResponse(env,start_response,response)
コード例 #20
0
 def __call__(self, env, start_response, *args, **kwargs):
     try:
         kwargs.update(self._extractParams(env))
     except ValueError as e:
         return vanilla.http_error(400, env, start_response, msg=e.message)
     return self.wrap(self.instance, env, start_response, *args, **kwargs)
コード例 #21
0
ファイル: restInterface.py プロジェクト: Magnil/fairywren
	def __call__(self,env,start_response,*args,**kwargs):
		try:
			kwargs.update(self._extractParams(env))
		except ValueError as e:
			return vanilla.http_error(400,env,start_response,msg=e.message)
		return self.wrap(self.instance,env,start_response,*args,**kwargs)