示例#1
0
	def test_get_request(self):
		db.c.update("DELETE FROM r4_listeners")
		db.c.update("DELETE FROM r4_request_store")
		u = User(2)
		u.authorize(1, None, None, True)
		u.put_in_request_line(1)
		# TODO: Use proper request class here instead of DB call
		db.c.update("INSERT INTO r4_listeners (sid, user_id, listener_icecast_id) VALUES (1, %s, 1)", (u.id,))
		db.c.update("INSERT INTO r4_request_store (user_id, song_id, sid) VALUES (%s, %s, 1)", (u.id, self.song1.id,))

		e = Election.create(1)
		req = e.get_request()
		self.assertNotEqual(None, req)
		self.assertEqual(self.song1.id, req.id)
示例#2
0
	def test_check_song_for_conflict(self):
		db.c.update("DELETE FROM r4_listeners")
		db.c.update("DELETE FROM r4_request_store")
	
		e = Election.create(1)
		self.assertEqual(False, e._check_song_for_conflict(self.song1))
		
		u = User(2)
		u.authorize(1, None, None, True)
		self.assertEqual(1, u.put_in_request_line(1))
		# TODO: Use proper request/user methods here instead of DB call
		db.c.update("UPDATE r4_request_line SET line_top_song_id = %s, line_expiry_tune_in = %s WHERE user_id = %s", (self.song1.id, time.time()+9999, u.id))
		db.c.update("INSERT INTO r4_listeners (sid, user_id, listener_icecast_id) VALUES (1, %s, 1)", (u.id,))
		db.c.update("INSERT INTO r4_request_store (user_id, song_id, sid) VALUES (%s, %s, 1)", (u.id, self.song1.id))
		request.update_cache(1)
		cache.update_local_cache_for_sid(1)
		self.assertEqual(True, e._check_song_for_conflict(self.song1))
		self.assertEqual(False, e._check_song_for_conflict(self.song5))
		self.assertEqual(event.ElecSongTypes.conflict, self.song5.data['entry_type'])
		self.assertEqual(event.ElecSongTypes.request, self.song1.data['entry_type'])
示例#3
0
    def setup_rainwave_session_and_redirect(self, user_id, destination):
        session_id = str(uuid.uuid4())
        db.c.update(
            "INSERT INTO r4_sessions (session_id, user_id) VALUES (%s, %s)", (
                session_id,
                user_id,
            ))
        self.set_cookie("r4_session_id", session_id, expires_days=365)

        if destination == "app" or destination == "rw":
            user = User(user_id)
            user.authorize(1, None, bypass=True)
            self.redirect(
                "rw://%s:%[email protected]" % (user.id, user.ensure_api_key()), )
        elif destination == "rwpath":
            user = User(user_id)
            user.authorize(1, None, bypass=True)
            self.redirect(
                "rwpath://rainwave.cc/%s/%s" %
                (user.id, user.ensure_api_key()), )
        else:
            self.redirect("/")
示例#4
0
class WSHandler(tornado.websocket.WebSocketHandler):
	is_websocket = True
	local_only = False
	help_hidden = False
	locale = None
	sid = None
	uuid = None

	def check_origin(self, origin):
		if websocket_allow_from == "*":
			return True
		parsed_origin = urlparse.urlparse(origin)
		return parsed_origin.netloc.endswith(websocket_allow_from)

	def open(self, *args, **kwargs):
		super(WSHandler, self).open(*args, **kwargs)

		try:
			self.sid = int(args[0])
		except Exception:
			pass

		if not self.sid:
			self.write_message({ "wserror": { "tl_key": "missing_station_id", "text": self.locale.translate("missing_station_id") } })
			return
		if not self.sid in config.station_ids:
			self.write_message({ "wserror": { "tl_key": "invalid_station_id", "text": self.locale.translate("invalid_station_id") } })
			return

		self.locale = get_browser_locale(self)

		self.authorized = False

		self.msg_times = []
		self.throttled = False
		self.throttled_msgs = []

	def rw_finish(self, *args, **kwargs):
		self.close()

	def keep_alive(self):
		self.write_message({ "ping": { "timestamp": timestamp() }})

	def on_close(self):
		global sessions
		self.throttled_msgs = []
		if self.sid:
			sessions[self.sid].remove(self)
		super(WSHandler, self).on_close()

	def write_message(self, obj, *args, **kwargs):
		message = json.dumps(obj)
		try:
			super(WSHandler, self).write_message(message, *args, **kwargs)
		except tornado.websocket.WebSocketClosedError:
			self.on_close()
		except tornado.websocket.WebSocketError as e:
			log.exception("websocket", "WebSocket Error", e)
			try:
				self.close()
			except Exception:
				self.on_close()

	def refresh_user(self):
		self.user.refresh(self.sid)
		# TODO: DJ permission checks

	def process_throttle(self):
		if not len(self.throttled_msgs):
			self.throttled = False
			return
		self.throttled_msgs.sort()
		# log.debug("throttle", "Throttled with %s messages" % len(self.throttled_msgs))
		action = self.throttled_msgs[0]['action']
		msg = None
		if not action in nonunique_actions:
			msgs = [ m for m in self.throttled_msgs if m['action'] == action ]
			msg = msgs.pop()
			for m in msgs:
				if "message_id" in m and fieldtypes.zero_or_greater_integer(m['message_id']):
					self.write_message({
						"wsthrottle": { "tl_key": "websocket_throttle", "text": self.locale.translate("websocket_throttle") },
						"message_id": { "message_id": fieldtypes.zero_or_greater_integer(m['message_id']), "success": False, "tl_key": "websocket_throttle" }
					})
			self.throttled_msgs = [ m for m in self.throttled_msgs if m['action'] != action ]
			# log.debug("throttle", "Handling last throttled %s message." % action)
		else:
			msg = self.throttled_msgs.pop(0)
			# log.debug("throttle", "Handling last throttled %s message." % action)
		if msg:
			self._process_message(msg, is_throttle_process=True)
		tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=0.5), self.process_throttle)

	def should_vote_throttle(self):
		if not self.votes_by_key in votes_by:
			return 0

		vote_limit = 3
		# log.debug("vote_throttle", "%s - %s - %s" % (vote_limit, votes_by[self.votes_by_key], (timestamp() < (last_vote_by[self.votes_by_key] + vote_once_every_seconds))))
		if (votes_by[self.votes_by_key] >= vote_limit) and (timestamp() < (last_vote_by[self.votes_by_key] + vote_once_every_seconds)):
			# log.debug("vote_throttle", (last_vote_by[self.votes_by_key] + vote_once_every_seconds) - timestamp())
			return (last_vote_by[self.votes_by_key] + vote_once_every_seconds) - timestamp()
		return 0

	def on_message(self, message_text):
		try:
			message = WSMessage()
			message.update(json.loads(message_text))
		except:
			self.write_message({ "wserror": { "tl_key": "invalid_json", "text": self.locale.translate("invalid_json") } })
			return

		if not "action" in message or not message['action']:
			self.write_message({ "wserror": { "tl_key": "missing_argument", "text": self.locale.translate("missing_argument", argument="action") } })
			return

		if not self.authorized and message['action'] != "auth":
			self.write_message({ "wserror": { "tl_key": "auth_required", "text": self.locale.translate("auth_required") } })
			return

		if not self.authorized and message['action'] == "auth":
			self._do_auth(message)
			return

		if message['action'] == "vote":
			if not message['entry_id']:
				self.write_message({ "wserror": { "tl_key": "missing_argument", "text": self.locale.translate("missing_argument", argument="entry_id") } })
			elif not fieldtypes.integer(message['entry_id']):
				self.write_message({ "wserror": { "tl_key": "invalid_argument", "text": self.locale.translate("missing_argument", argument="entry_id", reason=fieldtypes.integer_error) } })
			message['elec_id'] = rainwave.schedule.get_elec_id_for_entry(self.sid, message['entry_id'])

		self._process_message(message)

	def _process_message(self, message, is_throttle_process=False):
		message_id = None
		if "message_id" in message:
			message_id = fieldtypes.zero_or_greater_integer(message['message_id'])

		throt_t = timestamp() - 3
		self.msg_times = [ t for t in self.msg_times if t > throt_t ]

		if not is_throttle_process:
			self.msg_times.append(timestamp())
			# log.debug("throttle", "%s - %s" % (len(self.msg_times), message['action']))
			if self.throttled:
				# log.debug("throttle", "Currently throttled, adding to queue.")
				self.throttled_msgs.append(message)
				return
			elif len(self.msg_times) >= 5:
				# log.debug("throttle", "Too many messages, throttling.")
				self.throttled = True
				self.throttled_msgs.append(message)
				tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=0.5), self.process_throttle)
				return

		if message['action'] == "ping":
			self.write_message({ "pong": { "timestamp": timestamp() } })
			return

		if message['action'] == "pong":
			self.write_message({ "pongConfirm": { "timestamp": timestamp() } })
			return

		if message['action'] == "vote":
			zeromq.publish({ "action": "vote_by", "by": self.votes_by_key })

		if message['action'] == "check_sched_current_id":
			self._do_sched_check(message)
			return

		message['action'] = "/api4/%s" % message['action']
		if not message['action'] in api_endpoints:
			self.write_message({ "wserror": { "tl_key": "websocket_404", "text": self.locale.translate("websocket_404") } })
			return

		endpoint = api_endpoints[message['action']](websocket=True)
		endpoint.locale = self.locale
		endpoint.request = FakeRequestObject(message, self.request.cookies)
		endpoint.sid = message['sid'] if ('sid' in message and message['sid']) else self.sid
		endpoint.user = self.user
		#pylint: disable=W0212
		try:
			startclock = timestamp()
			# TODO: this should be a part of prepare_standalone!
			# it's required to see if another person on the same IP address has overriden the vote
			# for the in-memory user here, so it requires a DB fetch.
			if message['action'] == "/api4/vote" and self.user.is_anonymous():
				self.user.refresh(self.sid)
			if "message_id" in message:
				if message_id == None:
					endpoint.prepare_standalone()
					raise APIException("invalid_argument", argument="message_id", reason=fieldtypes.zero_or_greater_integer_error, http_code=400)
				endpoint.prepare_standalone(message_id)
			else:
				endpoint.prepare_standalone()
			endpoint.post()
			endpoint.append("api_info", { "exectime": timestamp() - startclock, "time": round(timestamp()) })
			if endpoint.sync_across_sessions:
				if endpoint.return_name in endpoint._output and isinstance(endpoint._output[endpoint.return_name], dict) and not endpoint._output[endpoint.return_name]['success']:
					pass
				else:
					zeromq.publish({ "action": "result_sync", "sid": self.sid, "user_id": self.user.id, "data": endpoint._output, "uuid_exclusion": self.uuid })
			if message['action'] == "/api4/vote" and endpoint.return_name in endpoint._output and isinstance(endpoint._output[endpoint.return_name], dict) and endpoint._output[endpoint.return_name]['success']:
				live_voting = rainwave.schedule.update_live_voting(self.sid)
				endpoint.append("live_voting", live_voting)
				if self.should_vote_throttle():
					zeromq.publish({ "action": "delayed_live_voting", "sid": self.sid, "uuid_exclusion": self.uuid, "data": { "live_voting": live_voting } })
				else:
					zeromq.publish({ "action": "live_voting", "sid": self.sid, "uuid_exclusion": self.uuid, "data": { "live_voting": live_voting } })
		except Exception as e:
			endpoint.write_error(500, exc_info=sys.exc_info(), no_finish=True)
			log.exception("websocket", "API Exception during operation.", e)
		finally:
			self.write_message(endpoint._output)
		#pylint: enable=W0212

	def update(self):
		handler = APIHandler(websocket=True)
		handler.locale = self.locale
		handler.request = FakeRequestObject({}, self.request.cookies)
		handler.sid = self.sid
		handler.user = self.user
		handler.return_name = "sync_result"
		try:
			startclock = timestamp()
			handler.prepare_standalone()

			if not cache.get_station(self.sid, "backend_ok"):
				raise APIException("station_offline")

			self.refresh_user()
			api_requests.info.attach_info_to_request(handler, live_voting=True)
			if self.user.is_dj():
				api_requests.info.attach_dj_info_to_request(handler)
			handler.append("user", self.user.to_private_dict())
			handler.append("api_info", { "exectime": timestamp() - startclock, "time": round(timestamp()) })
		except Exception as e:
			if handler:
				handler.write_error(500, exc_info=sys.exc_info(), no_finish=True)
			log.exception("websocket", "Exception during update.", e)
		finally:
			if handler:
				self.write_message(handler._output) 	#pylint: disable=W0212

	def update_user(self):
		self.write_message({ "user": self.user.to_private_dict() })

	def login_mixup_warn(self):
		self.write_message({ "sync_result": { "tl_key": "redownload_m3u", "text": self.locale.translate("redownload_m3u") } })

	def _do_auth(self, message):
		try:
			if not "user_id" in message or not message['user_id']:
				self.write_message({ "wserror": { "tl_key": "missing_argument", "text": self.locale.translate("missing_argument", argument="user_id") } })
			if not isinstance(message['user_id'], numbers.Number):
				self.write_message({ "wserror": { "tl_key": "invalid_argument", "text": self.locale.translate("invalid_argument", argument="user_id") } })
			if not "key" in message or not message['key']:
				self.write_message({ "wserror": { "tl_key": "missing_argument", "text": self.locale.translate("missing_argument", argument="key") } })

			self.user = User(message['user_id'])
			self.user.ip_address = self.request.remote_ip
			self.user.authorize(None, message['key'])
			if not self.user.authorized:
				self.write_message({ "wserror": { "tl_key": "auth_failed", "text": self.locale.translate("auth_failed") } })
				self.close()
				return
			self.authorized = True
			self.uuid = str(uuid.uuid4())

			global sessions
			sessions[self.sid].append(self)

			self.votes_by_key = self.request.remote_ip if self.user.is_anonymous() else self.user.id

			self.refresh_user()
			# no need to send the user's data to the user as that would have come with bootstrap
			# and will come with each synchronization of the schedule anyway
			self.write_message({ "wsok": True })
			# since this will be the first action in any websocket interaction though,
			# it'd be a good time to send a station offline message.
			self._station_offline_check()
		except Exception as e:
			log.exception("websocket", "Exception during authentication.", e)
			self.close()

	def _station_offline_check(self):
		if not cache.get_station(self.sid, "backend_ok"):
			# shamelessly fake an error.
			self.write_message({ "sync_result": { "tl_key": "station_offline", "text": self.locale.translate("station_offline") } })

	def _do_sched_check(self, message):
		if not "sched_id" in message or not message['sched_id']:
			self.write_message({ "wserror": { "tl_key": "missing_argument", "text": self.locale.translate("missing_argument", argument="sched_id") } })
			return
		if not isinstance(message['sched_id'], numbers.Number):
			self.write_message({ "wserror": { "tl_key": "invalid_argument", "text": self.locale.translate("invalid_argument", argument="sched_id") } })

		self._station_offline_check()

		if cache.get_station(self.sid, "sched_current_dict") and (cache.get_station(self.sid, "sched_current_dict")['id'] != message['sched_id']):
			self.update()
			self.write_message({ "outdated_data_warning": { "outdated": True } })
示例#5
0
class RainwaveHandler(tornado.web.RequestHandler):
    # The following variables can be overridden by you.
    # Fields is a hash with { "form_name" => (fieldtypes.[something], True|False|None) } format, so that automatic form validation can be done for you.  True/False values are for required/optional.
    # A True requirement means it must be present and valid
    # A False requirement means it is optional, but if present, must be valid
    # A None requirement means it is optional, and if present and invalid, will be set to None
    fields = {}
    # This URL variable is setup by the server decorator - DON'T TOUCH IT.
    url = False
    # Do we need a Rainwave auth key for this request?
    auth_required = True
    # return_name is used for documentation, can be an array.
    # If not inherited, return_key automatically turns into url + "_result".  Useful for simple requests like rate, vote, etc.
    return_name = None
    # Validate user's tuned in status first.
    tunein_required = False
    # Validate user's logged in status first.
    login_required = False
    # User must be a DJ for the next, current, or history[0] event
    dj_required = False
    # User must have an unused DJ-able event in the future
    dj_preparation = False
    # Validate user is a station administrator.
    admin_required = False
    # Do we need a valid SID as part of the submitted form?
    sid_required = True
    # Description string for documentation.
    description = "Undocumented."
    # Error codes for documentation.
    return_codes = None
    # Restricts requests to config.get("api_trusted_ip_addresses") (presumably 127.0.0.1)
    local_only = False
    # Should the user be free to vote and rate?
    unlocked_listener_only = False
    # Do we allow GET HTTP requests to this URL?  (standard is "no")
    allow_get = False
    # Use phpBB session/token auth?
    phpbb_auth = False
    # Does the user need perks (donor/beta/etc) to see this request/page?
    perks_required = False
    # hide from help, meant really only for things like redirect pages
    help_hidden = False
    # automatically add pagination to an API request.  use self.get_sql_limit_string()!
    pagination = False
    # allow access to station ID 0
    allow_sid_zero = False
    # set to allow from any source
    allow_cors = False
    # sync result across all user's websocket sessions
    sync_across_sessions = False

    def __init__(self, *args, **kwargs):
        if not 'websocket' in kwargs:
            super(RainwaveHandler, self).__init__(*args, **kwargs)
        self.cleaned_args = {}
        self.sid = None
        self._startclock = timestamp()
        self.user = None
        self._output = None
        self._output_array = False
        self.mobile = False

    def initialize(self, **kwargs):
        super(RainwaveHandler, self).initialize(**kwargs)
        if self.pagination:
            self.fields['per_page'] = (fieldtypes.zero_or_greater_integer,
                                       False)
            self.fields['page_start'] = (fieldtypes.zero_or_greater_integer,
                                         False)

    def set_cookie(self, name, value, *args, **kwargs):
        if isinstance(value, (int, long)):
            value = repr(value)
        super(RainwaveHandler, self).set_cookie(name, value, *args, **kwargs)

    def get_argument(self, name, default=None, **kwargs):
        if name in self.cleaned_args:
            return self.cleaned_args[name]
        if name in self.request.arguments:
            if isinstance(self.request.arguments[name], list):
                return self.request.arguments[name][-1].strip()
            return self.request.arguments[name]
        return default

    def set_argument(self, name, value):
        self.cleaned_args[name] = value

    def get_browser_locale(self, default="en_CA"):
        return get_browser_locale(self, default)

    def setup_output(self):
        if not self.return_name:
            self.return_name = self.url[self.url.rfind("/") + 1:] + "_result"
        else:
            self.return_name = self.return_name

    def arg_parse(self):
        for field, field_attribs in self.__class__.fields.iteritems():
            type_cast, required = field_attribs
            parsed = None
            if required and field not in self.request.arguments:
                raise APIException("missing_argument",
                                   argument=field,
                                   http_code=400)
            elif field in self.request.arguments:
                parsed = type_cast(self.get_argument(field), self)
                if parsed == None and required != None:
                    raise APIException(
                        "invalid_argument",
                        argument=field,
                        reason="%s %s" %
                        (field,
                         getattr(fieldtypes, "%s_error" % type_cast.__name__)),
                        http_code=400)
            self.cleaned_args[field] = parsed

    def sid_check(self):
        if self.sid is None and not self.sid_required:
            self.sid = config.get("default_station")
        if self.sid == 0 and self.allow_sid_zero:
            pass
        elif not self.sid in config.station_ids:
            raise APIException("invalid_station_id", http_code=400)

    def permission_checks(self):
        if (self.login_required or self.admin_required
                or self.dj_required) and (not self.user
                                          or self.user.is_anonymous()):
            raise APIException("login_required", http_code=403)
        if self.tunein_required and (not self.user
                                     or not self.user.is_tunedin()):
            raise APIException("tunein_required", http_code=403)
        if self.admin_required and (not self.user or not self.user.is_admin()):
            raise APIException("admin_required", http_code=403)
        if self.perks_required and (not self.user
                                    or not self.user.has_perks()):
            raise APIException("perks_required", http_code=403)

        if self.unlocked_listener_only and not self.user:
            raise APIException("auth_required", http_code=403)
        elif self.unlocked_listener_only and self.user.data[
                'lock'] and self.user.data['lock_sid'] != self.sid:
            raise APIException(
                "unlocked_only",
                station=config.station_id_friendly[self.user.data['lock_sid']],
                lock_counter=self.user.data['lock_counter'],
                http_code=403)

        is_dj = False
        if self.dj_required and not self.user:
            raise APIException("dj_required", http_code=403)
        if self.dj_required and not self.user.is_admin():
            potential_djs = cache.get_station(self.sid, "dj_user_ids")
            if not potential_djs or not self.user.id in potential_djs:
                raise APIException("dj_required", http_code=403)
            is_dj = True
            self.user.data['dj'] = True
        elif self.dj_required and self.user.is_admin():
            is_dj = True
            self.user.data['dj'] = True

        if self.dj_preparation and not is_dj and not self.user.is_admin():
            if not db.c.fetch_var(
                    "SELECT COUNT(*) FROM r4_schedule WHERE sched_used = 0 AND sched_dj_user_id = %s",
                (self.user.id, )):
                raise APIException("dj_required", http_code=403)

    # Called by Tornado, allows us to setup our request as we wish. User handling, form validation, etc. take place here.
    def prepare(self):
        if self.local_only and not self.request.remote_ip in config.get(
                "api_trusted_ip_addresses"):
            log.info(
                "api", "Rejected %s request from %s, untrusted address." %
                (self.url, self.request.remote_ip))
            raise APIException(
                "rejected", text="You are not coming from a trusted address.")

        if self.allow_cors:
            self.set_header("Access-Control-Allow-Origin", "*")
            self.set_header("Access-Control-Max-Age", "600")
            self.set_header("Access-Control-Allow-Credentials", "false")

        if not isinstance(self.locale, locale.RainwaveLocale):
            self.locale = self.get_browser_locale()

        self.setup_output()

        if 'in_order' in self.request.arguments:
            self._output = []
            self._output_array = True
        else:
            self._output = {}

        self.sid = fieldtypes.integer(self.get_cookie("r4_sid", None))
        hostname = self.request.headers.get('Host', None)
        if hostname:
            hostname = unicode(hostname).split(":")[0]
            if hostname in config.station_hostnames:
                self.sid = config.station_hostnames[hostname]
        sid_arg = fieldtypes.integer(self.get_argument("sid", None))
        if sid_arg is not None:
            self.sid = sid_arg
        if self.sid is None and self.sid_required:
            raise APIException("missing_station_id", http_code=400)

        self.arg_parse()

        self.sid_check()

        if self.sid:
            self.set_cookie("r4_sid",
                            str(self.sid),
                            expires_days=365,
                            domain=config.get("cookie_domain"))

        if self.phpbb_auth:
            self.do_phpbb_auth()
        else:
            self.rainwave_auth()

        if not self.user and self.auth_required:
            raise APIException("auth_required", http_code=403)
        elif not self.user and not self.auth_required:
            self.user = User(1)
            self.user.ip_address = self.request.remote_ip

        self.user.refresh(self.sid)

        if self.user and config.get("store_prefs"):
            self.user.save_preferences(self.request.remote_ip,
                                       self.get_cookie("r4_prefs", None))

        self.permission_checks()

    # works without touching cookies or headers, primarily used for websocket requests
    def prepare_standalone(self, message_id=None):
        self._output = {}
        if message_id != None:
            self.append("message_id", {"message_id": message_id})
        self.setup_output()
        self.arg_parse()
        self.sid_check()
        self.permission_checks()

    def do_phpbb_auth(self):
        phpbb_cookie_name = config.get("phpbb_cookie_name") + "_"
        user_id = fieldtypes.integer(
            self.get_cookie(phpbb_cookie_name + "u", ""))
        if not user_id:
            pass
        else:
            if self._verify_phpbb_session(user_id):
                # update_phpbb_session is done by verify_phpbb_session if successful
                self.user = User(user_id)
                self.user.ip_address = self.request.remote_ip
                self.user.authorize(self.sid, None, bypass=True)
                return True

            if not self.user and self.get_cookie(phpbb_cookie_name + "k"):
                can_login = db.c.fetch_var(
                    "SELECT 1 FROM phpbb_sessions_keys WHERE key_id = %s AND user_id = %s",
                    (hashlib.md5(self.get_cookie(phpbb_cookie_name +
                                                 "k")).hexdigest(), user_id))
                if can_login == 1:
                    self._update_phpbb_session(
                        self._get_phpbb_session(user_id))
                    self.user = User(user_id)
                    self.user.ip_address = self.request.remote_ip
                    self.user.authorize(self.sid, None, bypass=True)
                    return True
        return False

    def _verify_phpbb_session(self, user_id=None):
        # TODO: Do we want to enhance this with IP checking and other bits and pieces like phpBB does?
        if not user_id and not self.user:
            return None
        if not user_id:
            user_id = self.user.id
        cookie_session = self.get_cookie(
            config.get("phpbb_cookie_name") + "_sid")
        if cookie_session:
            if cookie_session == db.c.fetch_var(
                    "SELECT session_id FROM phpbb_sessions WHERE session_user_id = %s AND session_id = %s",
                (user_id, cookie_session)):
                self._update_phpbb_session(cookie_session)
                return cookie_session
        return None

    def _get_phpbb_session(self, user_id=None):
        return db.c.fetch_var(
            "SELECT session_id FROM phpbb_sessions WHERE session_user_id = %s ORDER BY session_last_visit DESC LIMIT 1",
            (user_id, ))

    def _update_phpbb_session(self, session_id):
        db.c.update(
            "UPDATE phpbb_sessions SET session_last_visit = %s, session_page = %s WHERE session_id = %s",
            (int(timestamp()), "rainwave", session_id))

    def rainwave_auth(self):
        user_id_present = "user_id" in self.request.arguments

        if self.auth_required and not user_id_present:
            raise APIException("missing_argument",
                               argument="user_id",
                               http_code=400)
        if user_id_present and not fieldtypes.numeric(
                self.get_argument("user_id")):
            # do not spit out the user ID back at them, that would create a potential XSS hack
            raise APIException("invalid_argument",
                               argument="user_id",
                               reason="not numeric.",
                               http_code=400)
        if (self.auth_required
                or user_id_present) and not "key" in self.request.arguments:
            raise APIException("missing_argument",
                               argument="key",
                               http_code=400)

        if user_id_present:
            self.user = User(long(self.get_argument("user_id")))
            self.user.ip_address = self.request.remote_ip
            self.user.authorize(self.sid, self.get_argument("key"))
            if not self.user.authorized:
                raise APIException("auth_failed", http_code=403)
            else:
                self._update_phpbb_session(
                    self._get_phpbb_session(self.user.id))

    # Handles adding dictionaries for JSON output
    # Will return a "code" if it exists in the hash passed in, if not, returns True
    def append(self, key, dct):
        if dct == None:
            return
        if self._output_array:
            self._output.append({key: dct})
        else:
            self._output[key] = dct
        if isinstance(dct, dict) and "code" in dct:
            return dct["code"]
        return True

    def append_standard(self,
                        tl_key,
                        text=None,
                        success=True,
                        return_name=None,
                        **kwargs):
        if not text:
            text = self.locale.translate(tl_key, **kwargs)
        kwargs.update({"success": success, "tl_key": tl_key, "text": text})
        if return_name:
            self.append(return_name, kwargs)
        else:
            self.append(self.return_name, kwargs)

    def write_error(self, status_code, **kwargs):
        if kwargs.has_key("exc_info"):
            exc = kwargs['exc_info'][1]
            if isinstance(exc, APIException):
                exc.localize(self.locale)
                log.debug("exception", exc.reason)

    def get_sql_limit_string(self):
        if not self.pagination:
            return ""
        limit = ""
        if self.get_argument("per_page") != None:
            if not self.get_argument("per_page"):
                limit = "LIMIT ALL"
            else:
                limit = "LIMIT %s" % self.get_argument("per_page")
        else:
            limit = "LIMIT 100"
        if self.get_argument("page_start"):
            limit += " OFFSET %s" % self.get_argument("page_start")
        return limit
示例#6
0
文件: web.py 项目: Dinir/rainwave
class RainwaveHandler(tornado.web.RequestHandler):
    # The following variables can be overridden by you.
    # Fields is a hash with { "form_name" => (fieldtypes.[something], True|False|None } format, so that automatic form validation can be done for you.  True/False values are for required/optional.
    # A True requirement means it must be present and valid
    # A False requirement means it is optional, but if present, must be valid
    # A None requirement means it is optional, and if present and invalid, will be set to None
    fields = {}
    # This URL variable is setup by the server decorator - DON'T TOUCH IT.
    url = False
    # Do we need a Rainwave auth key for this request?
    auth_required = True
    # return_name is used for documentation, can be an array.
    # If not inherited, return_key automatically turns into url + "_result".  Useful for simple requests like rate, vote, etc.
    return_name = None
    # Validate user's tuned in status first.
    tunein_required = False
    # Validate user's logged in status first.
    login_required = False
    # Validate user is a station administrator.
    admin_required = False
    # Validate user is currently DJing.
    dj_required = False
    # Do we need a valid SID as part of the submitted form?
    sid_required = True
    # Description string for documentation.
    description = "Undocumented."
    # Error codes for documentation.
    return_codes = None
    # Restricts requests to config.get("api_trusted_ip_addresses") (presumably 127.0.0.1)
    local_only = False
    # Should the user be free to vote and rate?
    unlocked_listener_only = False
    # Do we allow GET HTTP requests to this URL?  (standard is "no")
    allow_get = False
    # Use phpBB session/token auth?
    phpbb_auth = False
    # Does the user need perks (donor/beta/etc) to see this request/page?
    perks_required = False
    # hide from help, meant really only for things like redirect pages
    help_hidden = False
    # automatically add pagination to an API request.  use self.get_sql_limit_string()!
    pagination = False

    def __init__(self, *args, **kwargs):
        super(RainwaveHandler, self).__init__(*args, **kwargs)
        self.cleaned_args = {}
        self.cookie_prefs = {}
        self.sid = None
        self._startclock = time.time()
        self.user = None
        self._output = None
        self._output_array = False
        self.mobile = False

    def initialize(self, **kwargs):
        super(RainwaveHandler, self).initialize(**kwargs)
        if self.pagination:
            self.fields['per_page'] = (fieldtypes.zero_or_greater_integer,
                                       False)
            self.fields['page_start'] = (fieldtypes.zero_or_greater_integer,
                                         False)

    def set_cookie(self, name, value, *args, **kwargs):
        if isinstance(value, (int, long)):
            value = repr(value)
        super(RainwaveHandler, self).set_cookie(name, value, *args, **kwargs)

    def get_argument(self, name, *args, **kwargs):
        if name in self.cleaned_args:
            return self.cleaned_args[name]
        return super(RainwaveHandler, self).get_argument(name, *args, **kwargs)

    def set_argument(self, name, value):
        self.cleaned_args[name] = value

    def get_browser_locale(self, default="en_CA"):
        """Determines the user's locale from ``Accept-Language`` header.  Copied from Tornado, adapted slightly.

		See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
		"""
        if "rw_lang" in self.cookies:
            if locale.RainwaveLocale.exists(self.cookies['rw_lang'].value):
                return locale.RainwaveLocale.get(self.cookies['rw_lang'].value)
        if "Accept-Language" in self.request.headers:
            languages = self.request.headers["Accept-Language"].split(",")
            locales = []
            for language in languages:
                parts = language.strip().split(";")
                if len(parts) > 1 and parts[1].startswith("q="):
                    try:
                        score = float(parts[1][2:])
                    except (ValueError, TypeError):
                        score = 0.0
                else:
                    score = 1.0
                locales.append((parts[0], score))
            if locales:
                locales.sort(key=lambda pair: pair[1], reverse=True)
                codes = [l[0] for l in locales]
                return locale.RainwaveLocale.get_closest(codes)
        return locale.RainwaveLocale.get(default)

    # Called by Tornado, allows us to setup our request as we wish. User handling, form validation, etc. take place here.
    def prepare(self):
        if self.local_only and not self.request.remote_ip in config.get(
                "api_trusted_ip_addresses"):
            log.info(
                "api", "Rejected %s request from %s, untrusted address." %
                (self.url, self.request.remote_ip))
            raise APIException(
                "rejected", text="You are not coming from a trusted address.")

        if not isinstance(self.locale, locale.RainwaveLocale):
            self.locale = self.get_browser_locale()

        if not self.return_name:
            self.return_name = self.url[self.url.rfind("/") + 1:] + "_result"
        else:
            self.return_name = self.return_name

        if self.admin_required or self.dj_required:
            self.login_required = True

        if 'in_order' in self.request.arguments:
            self._output = []
            self._output_array = True
        else:
            self._output = {}

        self.sid = fieldtypes.integer(self.get_cookie("r4_sid", None))
        hostname = self.request.headers.get('Host', None)
        if hostname:
            hostname = unicode(hostname).split(":")[0]
            if hostname in config.station_hostnames:
                self.sid = config.station_hostnames[hostname]
        self.sid = fieldtypes.integer(self.get_argument("sid",
                                                        None)) or self.sid
        if self.sid and not self.sid in config.station_ids:
            self.sid = None
        if not self.sid and self.sid_required:
            raise APIException("missing_station_id", http_code=400)

        for field, field_attribs in self.__class__.fields.iteritems():
            type_cast, required = field_attribs
            if required and field not in self.request.arguments:
                raise APIException("missing_argument",
                                   argument=field,
                                   http_code=400)
            elif not required and field not in self.request.arguments:
                self.cleaned_args[field] = None
            else:
                parsed = type_cast(self.get_argument(field), self)
                if parsed == None and required != None:
                    raise APIException(
                        "invalid_argument",
                        argument=field,
                        reason="%s %s" %
                        (field,
                         getattr(fieldtypes, "%s_error" % type_cast.__name__)),
                        http_code=400)
                else:
                    self.cleaned_args[field] = parsed

        if not self.sid and not self.sid_required:
            self.sid = 5
        if not self.sid in config.station_ids:
            raise APIException("invalid_station_id", http_code=400)
        self.set_cookie("r4_sid",
                        str(self.sid),
                        expires_days=365,
                        domain=config.get("cookie_domain"))

        if self.phpbb_auth:
            self.do_phpbb_auth()
        else:
            self.rainwave_auth()

        if not self.user and self.auth_required:
            raise APIException("auth_required", http_code=403)
        elif not self.user and not self.auth_required:
            self.user = User(1)
            self.user.ip_address = self.request.remote_ip

        self.user.refresh(self.sid)

        if self.login_required and (not self.user or self.user.is_anonymous()):
            raise APIException("login_required", http_code=403)
        if self.tunein_required and (not self.user
                                     or not self.user.is_tunedin()):
            raise APIException("tunein_required", http_code=403)
        if self.admin_required and (not self.user or not self.user.is_admin()):
            raise APIException("admin_required", http_code=403)
        if self.dj_required and (not self.user or not self.user.is_dj()):
            raise APIException("dj_required", http_code=403)
        if self.perks_required and (not self.user
                                    or not self.user.has_perks()):
            raise APIException("perks_required", http_code=403)

        if self.unlocked_listener_only and not self.user:
            raise APIException("auth_required", http_code=403)
        elif self.unlocked_listener_only and self.user.data[
                'lock'] and self.user.data['lock_sid'] != self.sid:
            raise APIException(
                "unlocked_only",
                station=config.station_id_friendly[self.user.data['lock_sid']],
                lock_counter=self.user.data['lock_counter'],
                http_code=403)

    def do_phpbb_auth(self):
        phpbb_cookie_name = config.get("phpbb_cookie_name")
        user_id = fieldtypes.integer(
            self.get_cookie(phpbb_cookie_name + "u", ""))
        if not user_id:
            pass
        else:
            if self._verify_phpbb_session(user_id):
                # update_phpbb_session is done by verify_phpbb_session if successful
                self.user = User(user_id)
                self.user.ip_address = self.request.remote_ip
                self.user.authorize(self.sid, None, bypass=True)
                return True

            if not self.user and self.get_cookie(phpbb_cookie_name + "k"):
                can_login = db.c.fetch_var(
                    "SELECT 1 FROM phpbb_sessions_keys WHERE key_id = %s AND user_id = %s",
                    (hashlib.md5(self.get_cookie(phpbb_cookie_name +
                                                 "k")).hexdigest(), user_id))
                if can_login == 1:
                    self._update_phpbb_session(
                        self._get_phpbb_session(user_id))
                    self.user = User(user_id)
                    self.user.ip_address = self.request.remote_ip
                    self.user.authorize(self.sid, None, bypass=True)
                    return True
        return False

    def _verify_phpbb_session(self, user_id=None):
        # TODO: Do we want to enhance this with IP checking and other bits and pieces like phpBB does?
        if not user_id and not self.user:
            return None
        if not user_id:
            user_id = self.user.id
        cookie_session = self.get_cookie(
            config.get("phpbb_cookie_name") + "sid")
        if cookie_session:
            if cookie_session == db.c.fetch_var(
                    "SELECT session_id FROM phpbb_sessions WHERE session_user_id = %s AND session_id = %s",
                (user_id, cookie_session)):
                self._update_phpbb_session(cookie_session)
                return cookie_session
        return None

    def _get_phpbb_session(self, user_id=None):
        return db.c.fetch_var(
            "SELECT session_id FROM phpbb_sessions WHERE session_user_id = %s ORDER BY session_last_visit DESC LIMIT 1",
            (user_id, ))

    def _update_phpbb_session(self, session_id):
        db.c.update(
            "UPDATE phpbb_sessions SET session_last_visit = %s, session_page = %s WHERE session_id = %s",
            (int(time.time()), "rainwave", session_id))

    def rainwave_auth(self):
        user_id_present = "user_id" in self.request.arguments

        if self.auth_required and not user_id_present:
            raise APIException("missing_argument",
                               argument="user_id",
                               http_code=400)
        if user_id_present and not fieldtypes.numeric(
                self.get_argument("user_id")):
            # do not spit out the user ID back at them, that would create a potential XSS hack
            raise APIException("invalid_argument",
                               argument="user_id",
                               reason="not numeric.",
                               http_code=400)
        if (self.auth_required
                or user_id_present) and not "key" in self.request.arguments:
            raise APIException("missing_argument",
                               argument="key",
                               http_code=400)

        if user_id_present:
            self.user = User(long(self.get_argument("user_id")))
            self.user.ip_address = self.request.remote_ip
            self.user.authorize(self.sid, self.get_argument("key"))
            if not self.user.authorized:
                raise APIException("auth_failed", http_code=403)
            else:
                self._update_phpbb_session(
                    self._get_phpbb_session(self.user.id))

    # Handles adding dictionaries for JSON output
    # Will return a "code" if it exists in the hash passed in, if not, returns True
    def append(self, key, dct):
        if dct == None:
            return
        if self._output_array:
            self._output.append({key: dct})
        else:
            self._output[key] = dct
        if "code" in dct:
            return dct["code"]
        return True

    def append_standard(self,
                        tl_key,
                        text=None,
                        success=True,
                        return_name=None,
                        **kwargs):
        if not text:
            text = self.locale.translate(tl_key, **kwargs)
        kwargs.update({"success": success, "tl_key": tl_key, "text": text})
        if return_name:
            self.append(return_name, kwargs)
        else:
            self.append(self.return_name, kwargs)

    def write_error(self, status_code, **kwargs):
        if kwargs.has_key("exc_info"):
            exc = kwargs['exc_info'][1]
            if isinstance(exc, APIException):
                exc.localize(self.locale)
                log.debug("exception", exc.reason)

    def get_sql_limit_string(self):
        if not self.pagination:
            return ""
        limit = ""
        if self.get_argument("per_page") != None:
            if self.get_argument("per_page") == "0":
                limit = "LIMIT ALL"
            else:
                limit = "LIMIT %s" % self.get_argument("per_page")
        else:
            limit = "LIMIT 100"
        if self.get_argument("page_start"):
            limit += " OFFSET %s" % self.get_argument("page_start")
        return limit
示例#7
0
class WSHandler(tornado.websocket.WebSocketHandler):
	is_websocket = True
	local_only = False
	help_hidden = False
	locale = None
	sid = None
	uuid = None

	def check_origin(self, origin):
		if websocket_allow_from == "*":
			return True
		parsed_origin = urlparse.urlparse(origin)
		return parsed_origin.netloc.endswith(websocket_allow_from)

	def open(self, *args, **kwargs):
		super(WSHandler, self).open(*args, **kwargs)

		try:
			self.sid = int(args[0])
		except Exception:
			pass

		if not self.sid:
			self.write_message({ "wserror": { "tl_key": "missing_station_id", "text": self.locale.translate("missing_station_id") } })
			return
		if not self.sid in config.station_ids:
			self.write_message({ "wserror": { "tl_key": "invalid_station_id", "text": self.locale.translate("invalid_station_id") } })
			return

		self.locale = get_browser_locale(self)

		self.authorized = False

		self.msg_times = []
		self.throttled = False
		self.throttled_msgs = []

	def rw_finish(self, *args, **kwargs):
		self.close()

	def keep_alive(self):
		self.write_message({ "ping": { "timestamp": timestamp() }})

	def on_close(self):
		global sessions
		self.throttled_msgs = []
		if self.sid:
			sessions[self.sid].remove(self)
		super(WSHandler, self).on_close()

	def write_message(self, obj, *args, **kwargs):
		message = json.dumps(obj)
		try:
			super(WSHandler, self).write_message(message, *args, **kwargs)
		except tornado.websocket.WebSocketClosedError:
			self.on_close()
		except tornado.websocket.WebSocketError as e:
			log.exception("websocket", "WebSocket Error", e)
			try:
				self.close()
			except Exception:
				self.on_close()

	def refresh_user(self):
		self.user.refresh(self.sid)
		# TODO: DJ permission checks

	def process_throttle(self):
		if not len(self.throttled_msgs):
			self.throttled = False
			return
		self.throttled_msgs.sort()
		# log.debug("throttle", "Throttled with %s messages" % len(self.throttled_msgs))
		action = self.throttled_msgs[0]['action']
		msg = None
		if not action in nonunique_actions:
			msgs = [ m for m in self.throttled_msgs if m['action'] == action ]
			msg = msgs.pop()
			for m in msgs:
				if "message_id" in m and fieldtypes.zero_or_greater_integer(m['message_id']):
					self.write_message({
						"wsthrottle": { "tl_key": "websocket_throttle", "text": self.locale.translate("websocket_throttle") },
						"message_id": { "message_id": fieldtypes.zero_or_greater_integer(m['message_id']), "success": False, "tl_key": "websocket_throttle" }
					})
			self.throttled_msgs = [ m for m in self.throttled_msgs if m['action'] != action ]
			# log.debug("throttle", "Handling last throttled %s message." % action)
		else:
			msg = self.throttled_msgs.pop(0)
			# log.debug("throttle", "Handling last throttled %s message." % action)
		if msg:
			self._process_message(msg, is_throttle_process=True)
		tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=0.5), self.process_throttle)

	def should_vote_throttle(self):
		if not self.votes_by_key in votes_by:
			return 0

		vote_limit = 3
		# log.debug("vote_throttle", "%s - %s - %s" % (vote_limit, votes_by[self.votes_by_key], (timestamp() < (last_vote_by[self.votes_by_key] + vote_once_every_seconds))))
		if (votes_by[self.votes_by_key] >= vote_limit) and (timestamp() < (last_vote_by[self.votes_by_key] + vote_once_every_seconds)):
			# log.debug("vote_throttle", (last_vote_by[self.votes_by_key] + vote_once_every_seconds) - timestamp())
			return (last_vote_by[self.votes_by_key] + vote_once_every_seconds) - timestamp()
		return 0

	def on_message(self, message_text):
		try:
			message = WSMessage()
			message.update(json.loads(message_text))
		except:
			self.write_message({ "wserror": { "tl_key": "invalid_json", "text": self.locale.translate("invalid_json") } })
			return

		if not "action" in message or not message['action']:
			self.write_message({ "wserror": { "tl_key": "missing_argument", "text": self.locale.translate("missing_argument", argument="action") } })
			return

		if not self.authorized and message['action'] != "auth":
			self.write_message({ "wserror": { "tl_key": "auth_required", "text": self.locale.translate("auth_required") } })
			return

		if not self.authorized and message['action'] == "auth":
			self._do_auth(message)
			return

		if message['action'] == "vote":
			if not message['entry_id']:
				self.write_message({ "wserror": { "tl_key": "missing_argument", "text": self.locale.translate("missing_argument", argument="entry_id") } })
			elif not fieldtypes.integer(message['entry_id']):
				self.write_message({ "wserror": { "tl_key": "invalid_argument", "text": self.locale.translate("missing_argument", argument="entry_id", reason=fieldtypes.integer_error) } })
			message['elec_id'] = rainwave.schedule.get_elec_id_for_entry(self.sid, message['entry_id'])

		self._process_message(message)

	def _process_message(self, message, is_throttle_process=False):
		message_id = None
		if "message_id" in message:
			message_id = fieldtypes.zero_or_greater_integer(message['message_id'])

		throt_t = timestamp() - 3
		self.msg_times = [ t for t in self.msg_times if t > throt_t ]

		if not is_throttle_process:
			self.msg_times.append(timestamp())
			# log.debug("throttle", "%s - %s" % (len(self.msg_times), message['action']))
			if self.throttled:
				# log.debug("throttle", "Currently throttled, adding to queue.")
				self.throttled_msgs.append(message)
				return
			elif len(self.msg_times) >= 5:
				# log.debug("throttle", "Too many messages, throttling.")
				self.throttled = True
				self.throttled_msgs.append(message)
				tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=0.5), self.process_throttle)
				return

		if message['action'] == "ping":
			self.write_message({ "pong": { "timestamp": timestamp() } })
			return

		if message['action'] == "pong":
			self.write_message({ "pongConfirm": { "timestamp": timestamp() } })
			return

		if message['action'] == "vote":
			zeromq.publish({ "action": "vote_by", "by": self.votes_by_key })

		if message['action'] == "check_sched_current_id":
			self._do_sched_check(message)
			return

		message['action'] = "/api4/%s" % message['action']
		if not message['action'] in api_endpoints:
			self.write_message({ "wserror": { "tl_key": "websocket_404", "text": self.locale.translate("websocket_404") } })
			return

		endpoint = api_endpoints[message['action']](websocket=True)
		endpoint.locale = self.locale
		endpoint.request = FakeRequestObject(message, self.request.cookies)
		endpoint.sid = message['sid'] if ('sid' in message and message['sid']) else self.sid
		endpoint.user = self.user
		#pylint: disable=W0212
		try:
			startclock = timestamp()
			# TODO: this should be a part of prepare_standalone!
			# it's required to see if another person on the same IP address has overriden the vote
			# for the in-memory user here, so it requires a DB fetch.
			if message['action'] == "/api4/vote" and self.user.is_anonymous():
				self.user.refresh(self.sid)
			if "message_id" in message:
				if message_id == None:
					endpoint.prepare_standalone()
					raise APIException("invalid_argument", argument="message_id", reason=fieldtypes.zero_or_greater_integer_error, http_code=400)
				endpoint.prepare_standalone(message_id)
			else:
				endpoint.prepare_standalone()
			endpoint.post()
			endpoint.append("api_info", { "exectime": timestamp() - startclock, "time": round(timestamp()) })
			if endpoint.sync_across_sessions:
				if endpoint.return_name in endpoint._output and isinstance(endpoint._output[endpoint.return_name], dict) and not endpoint._output[endpoint.return_name]['success']:
					pass
				else:
					zeromq.publish({ "action": "result_sync", "sid": self.sid, "user_id": self.user.id, "data": endpoint._output, "uuid_exclusion": self.uuid })
			if message['action'] == "/api4/vote" and endpoint.return_name in endpoint._output and isinstance(endpoint._output[endpoint.return_name], dict) and endpoint._output[endpoint.return_name]['success']:
				live_voting = rainwave.schedule.update_live_voting(self.sid)
				endpoint.append("live_voting", live_voting)
				if self.should_vote_throttle():
					zeromq.publish({ "action": "delayed_live_voting", "sid": self.sid, "uuid_exclusion": self.uuid, "data": { "live_voting": live_voting } })
				else:
					zeromq.publish({ "action": "live_voting", "sid": self.sid, "uuid_exclusion": self.uuid, "data": { "live_voting": live_voting } })
		except Exception as e:
			endpoint.write_error(500, exc_info=sys.exc_info(), no_finish=True)
			log.exception("websocket", "API Exception during operation.", e)
		finally:
			self.write_message(endpoint._output)
		#pylint: enable=W0212

	def update(self):
		handler = APIHandler(websocket=True)
		handler.locale = self.locale
		handler.request = FakeRequestObject({}, self.request.cookies)
		handler.sid = self.sid
		handler.user = self.user
		handler.return_name = "sync_result"
		try:
			startclock = timestamp()
			handler.prepare_standalone()

			if not cache.get_station(self.sid, "backend_ok"):
				raise APIException("station_offline")

			self.refresh_user()
			api_requests.info.attach_info_to_request(handler, live_voting=True)
			if self.user.is_dj():
				api_requests.info.attach_dj_info_to_request(handler)
			handler.append("user", self.user.to_private_dict())
			handler.append("api_info", { "exectime": timestamp() - startclock, "time": round(timestamp()) })
		except Exception as e:
			if handler:
				handler.write_error(500, exc_info=sys.exc_info(), no_finish=True)
			log.exception("websocket", "Exception during update.", e)
		finally:
			if handler:
				self.write_message(handler._output) 	#pylint: disable=W0212

	def update_user(self):
		self.write_message({ "user": self.user.to_private_dict() })

	def login_mixup_warn(self):
		self.write_message({ "sync_result": { "tl_key": "redownload_m3u", "text": self.locale.translate("redownload_m3u") } })

	def _do_auth(self, message):
		try:
			if not "user_id" in message or not message['user_id']:
				self.write_message({ "wserror": { "tl_key": "missing_argument", "text": self.locale.translate("missing_argument", argument="user_id") } })
			if not isinstance(message['user_id'], numbers.Number):
				self.write_message({ "wserror": { "tl_key": "invalid_argument", "text": self.locale.translate("invalid_argument", argument="user_id") } })
			if not "key" in message or not message['key']:
				self.write_message({ "wserror": { "tl_key": "missing_argument", "text": self.locale.translate("missing_argument", argument="key") } })

			self.user = User(message['user_id'])
			self.user.ip_address = self.request.remote_ip
			self.user.authorize(None, message['key'])
			if not self.user.authorized:
				self.write_message({ "wserror": { "tl_key": "auth_failed", "text": self.locale.translate("auth_failed") } })
				self.close()
				return
			self.authorized = True
			self.uuid = str(uuid.uuid4())

			global sessions
			sessions[self.sid].append(self)

			self.votes_by_key = self.request.remote_ip if self.user.is_anonymous() else self.user.id

			self.refresh_user()
			# no need to send the user's data to the user as that would have come with bootstrap
			# and will come with each synchronization of the schedule anyway
			self.write_message({ "wsok": True })
			# since this will be the first action in any websocket interaction though,
			# it'd be a good time to send a station offline message.
			self._station_offline_check()
		except Exception as e:
			log.exception("websocket", "Exception during authentication.", e)
			self.close()

	def _station_offline_check(self):
		if not cache.get_station(self.sid, "backend_ok"):
			# shamelessly fake an error.
			self.write_message({ "sync_result": { "tl_key": "station_offline", "text": self.locale.translate("station_offline") } })

	def _do_sched_check(self, message):
		if not "sched_id" in message or not message['sched_id']:
			self.write_message({ "wserror": { "tl_key": "missing_argument", "text": self.locale.translate("missing_argument", argument="sched_id") } })
			return
		if not isinstance(message['sched_id'], numbers.Number):
			self.write_message({ "wserror": { "tl_key": "invalid_argument", "text": self.locale.translate("invalid_argument", argument="sched_id") } })

		self._station_offline_check()

		if cache.get_station(self.sid, "sched_current_dict") and (cache.get_station(self.sid, "sched_current_dict")['id'] != message['sched_id']):
			self.update()
			self.write_message({ "outdated_data_warning": { "outdated": True } })
示例#8
0
文件: web.py 项目: Reani/rainwave
class RainwaveHandler(tornado.web.RequestHandler):
	# The following variables can be overridden by you.
	# Fields is a hash with { "form_name" => (fieldtypes.[something], True|False|None } format, so that automatic form validation can be done for you.  True/False values are for required/optional.
	# A True requirement means it must be present and valid
	# A False requirement means it is optional, but if present, must be valid
	# A None requirement means it is optional, and if present and invalid, will be set to None
	fields = {}
	# This URL variable is setup by the server decorator - DON'T TOUCH IT.
	url = False
	# Do we need a Rainwave auth key for this request?
	auth_required = True
	# return_name is used for documentation, can be an array.
	# If not inherited, return_key automatically turns into url + "_result".  Useful for simple requests like rate, vote, etc.
	return_name = None
	# Validate user's tuned in status first.
	tunein_required = False
	# Validate user's logged in status first.
	login_required = False
	# Validate user is a station administrator.
	admin_required = False
	# Validate user is currently DJing.
	dj_required = False
	# Do we need a valid SID as part of the submitted form?
	sid_required = True
	# Description string for documentation.
	description = "Undocumented."
	# Error codes for documentation.
	return_codes = None
	# Restricts requests to config.get("api_trusted_ip_addresses") (presumably 127.0.0.1)
	local_only = False
	# Should the user be free to vote and rate?
	unlocked_listener_only = False
	# Do we allow GET HTTP requests to this URL?  (standard is "no")
	allow_get = False
	# Use phpBB session/token auth?
	phpbb_auth = False
	# Does the user need perks (donor/beta/etc) to see this request/page?
	perks_required = False

	def initialize(self, **kwargs):
		super(RainwaveHandler, self).initialize(**kwargs)
		self.cleaned_args = {}

	def set_cookie(self, name, value, **kwargs):
		if isinstance(value, (int, long)):
			value = repr(value)
		super(RainwaveHandler, self).set_cookie(name, value, **kwargs)

	def get_argument(self, name):
		if name in self.cleaned_args:
			return self.cleaned_args[name]
		return super(RainwaveHandler, self).get_argument(name)

	def set_argument(self, name, value):
		self.cleaned_args[name] = value

	def get_browser_locale(self, default="en_CA"):
		"""Determines the user's locale from ``Accept-Language`` header.  Copied from Tornado, adapted slightly.

		See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
		"""
		if "Accept-Language" in self.request.headers:
			languages = self.request.headers["Accept-Language"].split(",")
			locales = []
			for language in languages:
				parts = language.strip().split(";")
				if len(parts) > 1 and parts[1].startswith("q="):
					try:
						score = float(parts[1][2:])
					except (ValueError, TypeError):
						score = 0.0
				else:
					score = 1.0
				locales.append((parts[0], score))
			if locales:
				locales.sort(key=lambda pair: pair[1], reverse=True)
				codes = [l[0] for l in locales]
				return locale.RainwaveLocale.get_closest(codes)
		return locale.RainwaveLocale.get(default)

	# Called by Tornado, allows us to setup our request as we wish. User handling, form validation, etc. take place here.
	def prepare(self):
		self._startclock = time.time()
		self.user = None

		if self.local_only and not self.request.remote_ip in config.get("api_trusted_ip_addresses"):
			log.info("api", "Rejected %s request from %s" % (self.url, self.request.remote_ip))
			self.set_status(403)
			self.finish()

		if not isinstance(self.locale, locale.RainwaveLocale):
			self.locale = self.get_browser_locale()

		if not self.return_name:
			self.return_name = self.url[self.url.rfind("/")+1:] + "_result"
		else:
			self.return_name = self.return_name

		if self.admin_required or self.dj_required:
			self.login_required = True

		if 'in_order' in self.request.arguments:
			self._output = []
			self._output_array = True
		else:
			self._output = {}
			self._output_array = False

		self.sid = fieldtypes.integer(self.get_cookie("r4_sid", "1"))
		if "sid" in self.request.arguments:
			self.sid = int(self.get_argument("sid"))
		elif not self.sid:
			for possible_sid in config.station_ids:
				if self.request.host == config.get_station(possible_sid, "host"):
					self.sid = possible_sid
					break
		if not self.sid and self.sid_required:
			raise APIException("missing_station_id", http_code=400)
		if self.sid and not self.sid in config.station_ids:
			raise APIException("invalid_station_id", http_code=400)
		self.set_cookie("r4_sid", str(self.sid), expires_days=365, domain=config.get("cookie_domain"))

		for field, field_attribs in self.__class__.fields.iteritems():
			type_cast, required = field_attribs
			if required and field not in self.request.arguments:
				raise APIException("missing_argument", argument=field, http_code=400)
			elif not required and field not in self.request.arguments:
				self.cleaned_args[field] = None
			else:
				parsed = type_cast(self.get_argument(field), self)
				if parsed == None and required != None:
					raise APIException("invalid_argument", argument=field, reason=getattr(fieldtypes, "%s_error" % type_cast.__name__), http_code=400)
				else:
					self.cleaned_args[field] = parsed

		if self.phpbb_auth:
			self.do_phpbb_auth()
		else:
			self.rainwave_auth()

		if self.auth_required and not self.user:
			raise APIException("auth_required", http_code=403)

		if self.login_required and (not self.user or self.user.is_anonymous()):
			raise APIException("login_required", http_code=403)
		if self.tunein_required and (not self.user or not self.user.is_tunedin()):
			raise APIException("tunein_required", http_code=403)
		if self.admin_required and (not self.user or not self.user.is_admin()):
			raise APIException("admin_required", http_code=403)
		if self.dj_required and (not self.user or not self.user.is_dj()):
			raise APIException("dj_required", http_code=403)
		if self.perks_required and (not self.user or not self.user.has_perks()):
			raise APIException("perks_required", http_code=403)

		if self.unlocked_listener_only and not self.user:
			raise APIException("auth_required", http_code=403)
		elif self.unlocked_listener_only and self.user.data['listener_lock'] and self.user.data['listener_lock_sid'] != self.sid:
			raise APIException("unlocked_only", station=config.station_id_friendly[self.user.data['listener_lock_sid']], lock_counter=self.user.data['listener_lock_counter'], http_code=403)

	def do_phpbb_auth(self):
		phpbb_cookie_name = config.get("phpbb_cookie_name")
		self.user = None
		if not fieldtypes.integer(self.get_cookie(phpbb_cookie_name + "u", "")):
			self.user = User(1)
		else:
			user_id = int(self.get_cookie(phpbb_cookie_name + "u"))
			if self._verify_phpbb_session(user_id):
				# update_phpbb_session is done by verify_phpbb_session if successful
				self.user = User(user_id)
				self.user.authorize(self.sid, None, None, True)
				return True

			if not self.user and self.get_cookie(phpbb_cookie_name + "k"):
				can_login = db.c_old.fetch_var("SELECT 1 FROM phpbb_sessions_keys WHERE key_id = %s AND user_id = %s", (hashlib.md5(self.get_cookie(phpbb_cookie_name + "k")).hexdigest(), user_id))
				if can_login == 1:
					self._update_phpbb_session(self._get_phpbb_session(user_id))
					self.user = User(user_id)
					self.user.authorize(self.sid, None, None, True)
					return True
		return False

	def _verify_phpbb_session(self, user_id = None):
		# TODO: Do we want to enhance this with IP checking and other bits and pieces like phpBB does?
		if not user_id and not self.user:
			return None
		if not user_id:
			user_id = self.user.id
		cookie_session = self.get_cookie(config.get("phpbb_cookie_name") + "sid")
		if cookie_session:
			if cookie_session == db.c_old.fetch_var("SELECT session_id FROM phpbb_sessions WHERE session_user_id = %s AND session_id = %s", (user_id, cookie_session)):
				self._update_phpbb_session(cookie_session)
				return cookie_session
		return None

	def _get_phpbb_session(self, user_id = None):
		return db.c_old.fetch_var("SELECT session_id FROM phpbb_sessions WHERE session_user_id = %s ORDER BY session_last_visit DESC LIMIT 1", (user_id,))

	def _update_phpbb_session(self, session_id):
		db.c_old.update("UPDATE phpbb_sessions SET session_last_visit = %s, session_page = %s WHERE session_id = %s", (int(time.time()), "rainwave", session_id))

	def rainwave_auth(self):
		user_id_present = "user_id" in self.request.arguments

		if self.auth_required and not user_id_present:
			raise APIException("missing_argument", argument="user_id", http_code=400)
		if user_id_present and not fieldtypes.numeric(self.get_argument("user_id")):
			# do not spit out the user ID back at them, that would create a potential XSS hack
			raise APIException("invalid_argument", argument="user_id", reason="not numeric.", http_code=400)
		if (self.auth_required or user_id_present) and not "key" in self.request.arguments:
			raise APIException("missing_argument", argument="key", http_code=400)

		self.user = None
		if user_id_present:
			self.user = User(long(self.get_argument("user_id")))
			self.user.authorize(self.sid, self.request.remote_ip, self.get_argument("key"))
			if not self.user.authorized:
				raise APIException("auth_failed", http_code=403)
				# In case the raise is suppressed
				self.user = None
			else:
				self._update_phpbb_session(self._get_phpbb_session(self.user.id))
				self.sid = self.user.request_sid

	# Handles adding dictionaries for JSON output
	# Will return a "code" if it exists in the hash passed in, if not, returns True
	def append(self, key, hash):
		if hash == None:
			return
		if self._output_array:
			self._output.append({ key: hash })
		else:
			self._output[key] = hash
		if "code" in hash:
			return hash["code"]
		return True

	def append_standard(self, tl_key, text = None, success = True, **kwargs):
		if not text:
			text = self.locale.translate(tl_key, **kwargs)
		kwargs.update({ "success": success, "tl_key": tl_key, "text": text })
		self.append(self.return_name, kwargs)
示例#9
0
class RainwaveHandler(tornado.web.RequestHandler):
	# The following variables can be overridden by you.
	# Fields is a hash with { "form_name" => (fieldtypes.[something], True|False|None) } format, so that automatic form validation can be done for you.  True/False values are for required/optional.
	# A True requirement means it must be present and valid
	# A False requirement means it is optional, but if present, must be valid
	# A None requirement means it is optional, and if present and invalid, will be set to None
	fields = {}
	# This URL variable is setup by the server decorator - DON'T TOUCH IT.
	url = False
	# Do we need a Rainwave auth key for this request?
	auth_required = True
	# return_name is used for documentation, can be an array.
	# If not inherited, return_key automatically turns into url + "_result".  Useful for simple requests like rate, vote, etc.
	return_name = None
	# Validate user's tuned in status first.
	tunein_required = False
	# Validate user's logged in status first.
	login_required = False
	# User must be a DJ for the next, current, or history[0] event
	dj_required = False
	# User must have an unused DJ-able event in the future
	dj_preparation = False
	# Validate user is a station administrator.
	admin_required = False
	# Do we need a valid SID as part of the submitted form?
	sid_required = True
	# Description string for documentation.
	description = "Undocumented."
	# Error codes for documentation.
	return_codes = None
	# Restricts requests to config.get("api_trusted_ip_addresses") (presumably 127.0.0.1)
	local_only = False
	# Should the user be free to vote and rate?
	unlocked_listener_only = False
	# Do we allow GET HTTP requests to this URL?  (standard is "no")
	allow_get = False
	# Use phpBB session/token auth?
	phpbb_auth = False
	# Does the user need perks (donor/beta/etc) to see this request/page?
	perks_required = False
	# hide from help, meant really only for things like redirect pages
	help_hidden = False
	# automatically add pagination to an API request.  use self.get_sql_limit_string()!
	pagination = False
	# allow access to station ID 0
	allow_sid_zero = False
	# set to allow from any source
	allow_cors = False
	# sync result across all user's websocket sessions
	sync_across_sessions = False

	def __init__(self, *args, **kwargs):
		if not 'websocket' in kwargs:
			super(RainwaveHandler, self).__init__(*args, **kwargs)
		self.cleaned_args = {}
		self.sid = None
		self._startclock = timestamp()
		self.user = None
		self._output = None
		self._output_array = False
		self.mobile = False

	def initialize(self, **kwargs):
		super(RainwaveHandler, self).initialize(**kwargs)
		if self.pagination:
 			self.fields['per_page'] = (fieldtypes.zero_or_greater_integer, False)
 			self.fields['page_start'] = (fieldtypes.zero_or_greater_integer, False)

	def set_cookie(self, name, value, *args, **kwargs):
		if isinstance(value, (int, long)):
			value = repr(value)
		super(RainwaveHandler, self).set_cookie(name, value, *args, **kwargs)

	def get_argument(self, name, default=None, **kwargs):
		if name in self.cleaned_args:
			return self.cleaned_args[name]
		if name in self.request.arguments:
			if isinstance(self.request.arguments[name], list):
				return self.request.arguments[name][-1].strip()
			return self.request.arguments[name]
		return default

	def set_argument(self, name, value):
		self.cleaned_args[name] = value

	def get_browser_locale(self, default="en_CA"):
		return get_browser_locale(self, default)

	def setup_output(self):
		if not self.return_name:
			self.return_name = self.url[self.url.rfind("/")+1:] + "_result"
		else:
			self.return_name = self.return_name

	def arg_parse(self):
		for field, field_attribs in self.__class__.fields.iteritems():
			type_cast, required = field_attribs
			parsed = None
			if required and field not in self.request.arguments:
				raise APIException("missing_argument", argument=field, http_code=400)
			elif field in self.request.arguments:
				parsed = type_cast(self.get_argument(field), self)
				if parsed == None and required != None:
					raise APIException("invalid_argument", argument=field, reason="%s %s" % (field, getattr(fieldtypes, "%s_error" % type_cast.__name__)), http_code=400)
			self.cleaned_args[field] = parsed

	def sid_check(self):
		if self.sid is None and not self.sid_required:
			self.sid = config.get("default_station")
		if self.sid == 0 and self.allow_sid_zero:
			pass
		elif not self.sid in config.station_ids:
			raise APIException("invalid_station_id", http_code=400)

	def permission_checks(self):
		if (self.login_required or self.admin_required or self.dj_required) and (not self.user or self.user.is_anonymous()):
			raise APIException("login_required", http_code=403)
		if self.tunein_required and (not self.user or not self.user.is_tunedin()):
			raise APIException("tunein_required", http_code=403)
		if self.admin_required and (not self.user or not self.user.is_admin()):
			raise APIException("admin_required", http_code=403)
		if self.perks_required and (not self.user or not self.user.has_perks()):
			raise APIException("perks_required", http_code=403)

		if self.unlocked_listener_only and not self.user:
			raise APIException("auth_required", http_code=403)
		elif self.unlocked_listener_only and self.user.data['lock'] and self.user.data['lock_sid'] != self.sid:
			raise APIException("unlocked_only", station=config.station_id_friendly[self.user.data['lock_sid']], lock_counter=self.user.data['lock_counter'], http_code=403)

		is_dj = False
		if self.dj_required and not self.user:
			raise APIException("dj_required", http_code=403)
		if self.dj_required and not self.user.is_admin():
			potential_djs = cache.get_station(self.sid, "dj_user_ids")
			if not potential_djs or not self.user.id in potential_djs:
				raise APIException("dj_required", http_code=403)
			is_dj = True
			self.user.data['dj'] = True
		elif self.dj_required and self.user.is_admin():
			is_dj = True
			self.user.data['dj'] = True

		if self.dj_preparation and not is_dj and not self.user.is_admin():
			if not db.c.fetch_var("SELECT COUNT(*) FROM r4_schedule WHERE sched_used = 0 AND sched_dj_user_id = %s", (self.user.id,)):
				raise APIException("dj_required", http_code=403)

	# Called by Tornado, allows us to setup our request as we wish. User handling, form validation, etc. take place here.
	def prepare(self):
		if self.local_only and not self.request.remote_ip in config.get("api_trusted_ip_addresses"):
			log.info("api", "Rejected %s request from %s, untrusted address." % (self.url, self.request.remote_ip))
			raise APIException("rejected", text="You are not coming from a trusted address.")

		if self.allow_cors:
			self.set_header("Access-Control-Allow-Origin", "*")
			self.set_header("Access-Control-Max-Age", "600")
			self.set_header("Access-Control-Allow-Credentials", "false")

		if not isinstance(self.locale, locale.RainwaveLocale):
			self.locale = self.get_browser_locale()

		self.setup_output()

		if 'in_order' in self.request.arguments:
			self._output = []
			self._output_array = True
		else:
			self._output = {}

		if not self.sid:
			self.sid = fieldtypes.integer(self.get_cookie("r4_sid", None))
			hostname = self.request.headers.get('Host', None)
			if hostname:
				hostname = unicode(hostname).split(":")[0]
				if hostname in config.station_hostnames:
					self.sid = config.station_hostnames[hostname]
			sid_arg = fieldtypes.integer(self.get_argument("sid", None))
			if sid_arg is not None:
				self.sid = sid_arg
			if self.sid is None and self.sid_required:
				raise APIException("missing_station_id", http_code=400)

		self.arg_parse()

		self.sid_check()

		if self.sid:
			self.set_cookie("r4_sid", str(self.sid), expires_days=365)

		if self.phpbb_auth:
			self.do_phpbb_auth()
		else:
			self.rainwave_auth()

		if not self.user and self.auth_required:
			raise APIException("auth_required", http_code=403)
		elif not self.user and not self.auth_required:
			self.user = User(1)
			self.user.ip_address = self.request.remote_ip

		self.user.refresh(self.sid)

		if self.user and config.get("store_prefs"):
			self.user.save_preferences(self.request.remote_ip, self.get_cookie("r4_prefs", None))

		self.permission_checks()

	# works without touching cookies or headers, primarily used for websocket requests
	def prepare_standalone(self, message_id=None):
		self._output = {}
		if message_id != None:
			self.append("message_id", { "message_id": message_id })
		self.setup_output()
		self.arg_parse()
		self.sid_check()
		self.permission_checks()

	def do_phpbb_auth(self):
		phpbb_cookie_name = config.get("phpbb_cookie_name") + "_"
		user_id = fieldtypes.integer(self.get_cookie(phpbb_cookie_name + "u", ""))
		if not user_id:
			pass
		else:
			if self._verify_phpbb_session(user_id):
				# update_phpbb_session is done by verify_phpbb_session if successful
				self.user = User(user_id)
				self.user.ip_address = self.request.remote_ip
				self.user.authorize(self.sid, None, bypass=True)
				return True

			if not self.user and self.get_cookie(phpbb_cookie_name + "k"):
				can_login = db.c.fetch_var("SELECT 1 FROM phpbb_sessions_keys WHERE key_id = %s AND user_id = %s", (hashlib.md5(self.get_cookie(phpbb_cookie_name + "k")).hexdigest(), user_id))
				if can_login == 1:
					self._update_phpbb_session(self._get_phpbb_session(user_id))
					self.user = User(user_id)
					self.user.ip_address = self.request.remote_ip
					self.user.authorize(self.sid, None, bypass=True)
					return True
		return False

	def _verify_phpbb_session(self, user_id = None):
		# TODO: Do we want to enhance this with IP checking and other bits and pieces like phpBB does?
		if not user_id and not self.user:
			return None
		if not user_id:
			user_id = self.user.id
		cookie_session = self.get_cookie(config.get("phpbb_cookie_name") + "_sid")
		if cookie_session:
			if cookie_session == db.c.fetch_var("SELECT session_id FROM phpbb_sessions WHERE session_user_id = %s AND session_id = %s", (user_id, cookie_session)):
				self._update_phpbb_session(cookie_session)
				return cookie_session
		return None

	def _get_phpbb_session(self, user_id = None):
		return db.c.fetch_var("SELECT session_id FROM phpbb_sessions WHERE session_user_id = %s ORDER BY session_last_visit DESC LIMIT 1", (user_id,))

	def _update_phpbb_session(self, session_id):
		db.c.update("UPDATE phpbb_sessions SET session_last_visit = %s, session_page = %s WHERE session_id = %s", (int(timestamp()), "rainwave", session_id))

	def rainwave_auth(self):
		user_id_present = "user_id" in self.request.arguments

		if self.auth_required and not user_id_present:
			raise APIException("missing_argument", argument="user_id", http_code=400)
		if user_id_present and not fieldtypes.numeric(self.get_argument("user_id")):
			# do not spit out the user ID back at them, that would create a potential XSS hack
			raise APIException("invalid_argument", argument="user_id", reason="not numeric.", http_code=400)
		if (self.auth_required or user_id_present) and not "key" in self.request.arguments:
			raise APIException("missing_argument", argument="key", http_code=400)

		if user_id_present:
			self.user = User(long(self.get_argument("user_id")))
			self.user.ip_address = self.request.remote_ip
			self.user.authorize(self.sid, self.get_argument("key"))
			if not self.user.authorized:
				raise APIException("auth_failed", http_code=403)
			else:
				self._update_phpbb_session(self._get_phpbb_session(self.user.id))

	# Handles adding dictionaries for JSON output
	# Will return a "code" if it exists in the hash passed in, if not, returns True
	def append(self, key, dct):
		if dct == None:
			return
		if self._output_array:
			self._output.append({ key: dct })
		else:
			self._output[key] = dct
		if isinstance(dct, dict) and "code" in dct:
			return dct["code"]
		return True

	def append_standard(self, tl_key, text = None, success = True, return_name = None, **kwargs):
		if not text:
			text = self.locale.translate(tl_key, **kwargs)
		kwargs.update({ "success": success, "tl_key": tl_key, "text": text })
		if return_name:
			self.append(return_name, kwargs)
		else:
			self.append(self.return_name, kwargs)

	def write_error(self, status_code, **kwargs):
		if kwargs.has_key("exc_info"):
			exc = kwargs['exc_info'][1]
			if isinstance(exc, APIException):
				exc.localize(self.locale)
				log.debug("exception", exc.reason)

	def get_sql_limit_string(self):
		if not self.pagination:
			return ""
		limit = ""
		if self.get_argument("per_page") != None:
			if not self.get_argument("per_page"):
				limit = "LIMIT ALL"
			else:
				limit = "LIMIT %s" % self.get_argument("per_page")
		else:
			limit = "LIMIT 100"
		if self.get_argument("page_start"):
			limit += " OFFSET %s" % self.get_argument("page_start")
		return limit
示例#10
0
文件: web.py 项目: rmcauley/rwbackend
class RequestHandler(tornado.web.RequestHandler):
	# The following variables can be overridden by you.
	# Fields is a hash with { "form_name" => (fieldtypes.[something], True|False } format, so that automatic form validation can be done for you.  True/False values are for required/optional.
	fields = {}
	# This URL variable is setup by the server decorator - DON'T TOUCH IT.
	url = False
	# Do we need a Rainwave auth key for this request?
	auth_required = True
	# return_name is used for documentation, can be an array.
	# If not inherited, return_key automatically turns into url + "_result".  Useful for simple requests like rate, vote, etc.
	return_name = False
	# Validate user's tuned in status first.
	tunein_required = False
	# Validate user's logged in status first.
	login_required = False
	# Validate user is a station administrator.
	admin_required = False
	# Validate user is currently DJing.
	dj_required = False
	# Do we need a valid SID as part of the submitted form?
	sid_required = True
	# Description string for documentation.
	description = "Undocumented."
	# Only for the backend to be called
	local_only = False
	# Should the user be free to vote and rate?
	unlocked_listener_only = False

	# Called by Tornado, allows us to setup our request as we wish. User handling, form validation, etc. take place here.
	def prepare(self):
		self._startclock = time.clock()
		self.request_ok = False
		self.user = None
		
		if self.local_only and not self.request.remote_ip in config.get("trusted_ips"):
			self.failed = True
			self.set_status(403)
			self.finish()

		if self.return_name == False:
			self.return_name = self.__class__.url + "_result"
		else:
			self.return_name = self.__class__.return_name
			
		if self.admin_required or self.dj_required:
			self.login_required = True
	
		if 'in_order' in self.request.arguments:
			self._output = []
			self._output_array = True
		else:
			self._output = {}
			self._output_array = False
			
		request_ok = True
	
		self.args = {}
		for field, field_attribs in self.__class__.fields.iteritems():
			type_cast, required = field_attribs
			if required and field not in self.request.arguments:
				self.append("error", api.returns.ErrorReturn(-1000, "Missing %s argument." % field))
				request_ok = False
			elif not required and field not in self.request.arguments:
				pass
			else:
				parsed = type_cast(self.get_argument(field))
				if parsed == None:
					self.append("error", api.returns.ErrorReturn(-1000, "Invalid argument %s: %s" % (field, getattr(fieldtypes, "%s_error" % type_cast.__name__))))
					request_ok = False
				else:
					self.args[field] = parsed

		self.sid = None
		if "sid" in self.request.arguments:
			self.sid = int(self.get_argument("sid"))
		elif self.sid_required:
			self.append("error", api.returns.ErrorReturn(-1000, "Missing station ID argument."))
			request_ok = False
		if request_ok and self.sid and not self.sid in config.station_ids:
			self.append("error", api.returns.ErrorReturn(-1000, "Invalid station ID."))
			request_ok = False
				
		if request_ok:
			authorized = self.rainwave_auth()
			if self.auth_required and not authorized:
				request_ok = False
				
		if self.unlocked_listener_only and self.user and self.user.data['listener_lock']:
			request_ok = False
			self.append("error", api.returns.ErrorReturn(-1000, "Listener locked to %s for %s more songs." % (config.station_id_friendly[self.user.data['listener_lock_sid']], self.user.data['listener_lock_count'])))
				
		self.request_ok = request_ok
		if not request_ok:
			self.finish()
	
	def rainwave_auth(self):
		request_ok = True
		user_id_present = "user_id" in self.request.arguments
		
		if self.auth_required and not user_id_present:
			self.append("error", api.returns.ErrorReturn(-1000, "Missing user_id argument."))
			request_ok = False
		if user_id_present and not fieldtypes.numeric(self.get_argument("user_id")):
			self.append("error", api.returns.ErrorReturn(-1000, "Invalid user ID %s."))
			request_ok = False
		if (self.auth_required or user_id_present) and not "key" in self.request.arguments:
			self.append("error", api.returns.ErrorReturn(-1000, "Missing 'key' argument."))
			request_ok = False
		
		self.user = None
		if request_ok and user_id_present:
			self.user = User(long(self.get_argument("user_id")))
			self.user.authorize(self.sid, self.request.remote_ip, self.get_argument("key"))
			if not self.user.authorized:
				self.append("error", api.returns.ErrorReturn(403, "Authorization failed."))
				request_ok = False
			else:
				self.sid = self.user.request_sid
		
		if self.auth_required and not self.user:
			self.append("error", api.returns.ErrorReturn(403, "Authorization required."))
			request_ok = False
		
		if self.user and request_ok:
			if self.login_required and not user.is_anonymous():
				self.append("error", api.returns.ErrorReturn(-1001, "Login required for %s." % url))
				request_ok = False
			if self.tunein_required and not user.is_tunedin():
				self.append("error", api.returns.ErrorReturn(-1001, "You must be tuned in to use %s." % url))
				request_ok = False
			if self.admin_required and not user.is_admin():
				self.append("error", api.returns.ErrorReturn(-1001, "You must be an admin to use %s." % url))
				request_ok = False
			if self.dj_required and not user.is_dj():
				self.append("error", api.returns.ErrorReturn(-1001, "You must be DJing to use %s." % url))
				request_ok = False
		
		return request_ok

	# Handles adding dictionaries for JSON output
	# Will return a "code" if it exists in the hash passed in, if not, returns True
	def append(self, key, hash):
		if hash == None:
			return
		if self._output_array:
			self._output.append({ key: hash })
		else:
			self._output[key] = hash
		if "code" in hash:
			return hash["code"]
		return True

	# Sends off the data to the user.
	def finish(self, chunk=None):
		self.set_header("Content-Type", "application/json")
		if hasattr(self, "_output"):
			if hasattr(self, "_startclock"):
				exectime = time.clock() - self._startclock
			else:
				exectime = -1
			self.append("api_info", { "exectime": exectime, "time": round(time.time()) })
			self.write(tornado.escape.json_encode(self._output))
		super(RequestHandler, self).finish(chunk)
示例#11
0
class MainIndex(tornado.web.RequestHandler):
	def prepare(self):
		# TODO: Language
		self.info = []
		self.sid = fieldtypes.integer(self.get_cookie("r4sid", "1"))
		if not self.sid:
			self.sid = 1
		
		if self.request.host == "game.rainwave.cc":
			self.sid = 1
		elif self.request.host == "ocr.rainwave.cc":
			self.sid = 2
		elif self.request.host == "covers.rainwave.cc" or self.request.host == "cover.rainwave.cc":
			self.sid = 3
		elif self.request.host == "chiptune.rainwave.cc":
			self.sid = 4
		elif self.request.host == "all.rainwave.cc":
			self.sid = 5
		
		self.set_cookie("r4sid", str(self.sid), expires_days=365, domain=".rainwave.cc")
	
		self.user = None
		if not fieldtypes.integer(self.get_cookie("phpbb3_38ie8_u", "")):
			self.user = User(1)
		else:
			user_id = int(self.get_cookie("phpbb3_38ie8_u"))
			if self.get_cookie("phpbb3_38ie8_sid"):
				session_id = db.c_old.fetch_var("SELECT session_id FROM phpbb_sessions WHERE session_id = %s AND session_user_id = %s", (self.get_cookie("phpbb3_38ie8_sid"), user_id))
				if session_id:
					db.c_old.update("UPDATE phpbb_sessions SET session_last_visit = %s, session_page = %s WHERE session_id = %s", (time.time(), "rainwave", session_id))
					self.user = User(user_id)
					self.user.authorize(self.sid, None, None, True)

			if not self.user and self.get_cookie("phpbb3_38ie8_k"):
				can_login = db.c_old.fetch_var("SELECT 1 FROM phpbb_sessions_keys WHERE key_id = %s AND user_id = %s", (hashlib.md5(self.get_cookie("phpbb3_38ie8_k")).hexdigest(), user_id))
				if can_login == 1:
					self.user = User(user_id)
					self.user.authorize(self.sid, None, None, True)

		if not self.user:
			self.user = User(1)
		self.user.ensure_api_key(self.request.remote_ip)
		self.user.data['sid'] = self.sid
		
	# this is so that get_info can be called, makes us compatible with the custom web handler used elsewhere in RW
	def append(self, key, value):
		self.info.append({ key: value })
		
	def get(self):
		info.attach_info_to_request(self)
		self.set_header("Content-Type", "text/plain")
		self.render("index.html", user=self.user, info=tornado.escape.json_encode(self.info), sid=self.sid)
		
# @handle_url("authtest_beta")
# class BetaIndex(MainIndex):
	# def get(self):
		# if self.user.data['group_id'] not in (5, 4, 8, 12, 15, 14, 17):
			# self.send_error(403)
		# else:
			# info.attach_info_to_request(self)
			# self.set_header("Content-Type", "text/plain")
			# self.render("index.html", user=self.user, info=tornado.escape.json_encode(self.info), sid=self.sid)