def getSchemaVersion(conn, latestVersions): info = {'schema-category': None, 'schema-version': None, 'schema-created': None, 'schema-latest-version': None} cur = conn.cursor() try: cur.execute("SELECT key, value FROM config WHERE key IN ('schema-category', 'schema-version', 'schema-created')") for row in cur.fetchall(): info[row[0]] = json_loads(row[1]) if row[1] is not None else None except: pass if info.has_key('schema-category'): if latestVersions.has_key(info['schema-category']): info['schema-latest-version'] = latestVersions[info['schema-category']] if info['schema-version'] is not None: info['schema-needs-upgrade'] = info['schema-version'] < info['schema-latest-version'] else: info['schema-needs-upgrade'] = False return info
def _login(self, txn, authResponse, stayLoggedIn): if self.authChallenge is None: raise Exception(URI_ERROR + "login-without-previous-request", "Login attempt without previous login request.") txn.execute("SELECT value FROM config WHERE key = ?", ['admin-password']) res = txn.fetchone() if res: pw = str(json_loads(res[0])) h = hmac.new(pw, self.authChallenge, hashlib.sha256) v = binascii.b2a_base64(h.digest()).strip() if v == str(authResponse): self.authUser = self.authChallengeUser if stayLoggedIn: cookie = ''.join([random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") for i in xrange(64)]) now = utcnow() txn.execute("INSERT INTO cookie (created, username, value) VALUES (?, ?, ?)", [now, "admin", cookie]) self.authCookie = cookie res = cookie else: res = None self.onLogin() return res else: raise Exception(URI_ERROR + "login-failed", "Login failed.") else: raise Exception(URI_ERROR + "internal-error", "Could not retrieve admin password from database.")
def getSchemaVersion(conn, latestVersions): info = { 'schema-category': None, 'schema-version': None, 'schema-created': None, 'schema-latest-version': None } cur = conn.cursor() try: cur.execute( "SELECT key, value FROM config WHERE key IN ('schema-category', 'schema-version', 'schema-created')" ) for row in cur.fetchall(): info[row[0]] = json_loads(row[1]) if row[1] is not None else None except: pass if info.has_key('schema-category'): if latestVersions.has_key(info['schema-category']): info['schema-latest-version'] = latestVersions[ info['schema-category']] if info['schema-version'] is not None: info['schema-needs-upgrade'] = info['schema-version'] < info[ 'schema-latest-version'] else: info['schema-needs-upgrade'] = False return info
def _onQueryApiResult(self, res, remote, apiRequest): """ Consume Ext.Direct API request result. """ ## cut out JSON with API definition ## FIXME: make this more robust! i1 = res.find(remote.apiObject) if i1 < 0: raise Exception(URI_ERROR_REMOTING, "API object could not be found in response payload", {'message': "The API object %s could not be found in the response payload" % remote.apiObject, 'response': res, 'request': apiRequest}) i2 = res.find("=", i1) + 1 r = res[i2:len(res) - 1].strip() ## parse JSON API definition try: o = json_loads(r) except Exception, e: raise Exception(URI_ERROR_REMOTING, "remoting API response payload could not be decoded", {'message': str(e), 'response': res, 'json': r, 'request': apiRequest})
def _getPasswordSet(self, txn): txn.execute("SELECT value FROM config WHERE key = ?", ["admin-password"]) res = txn.fetchone() if res: password = json_loads(res[0]) return password is not None else: raise Exception(URI_ERROR + "internal-error", "admin-password key not found.")
def _setupSchema(app, conn, uninstallOnly = False): cf = os.path.join(app.webdata, "demo", "demo.json") try: cfo = json_loads(open(cf).read()) except Exception, e: log.msg("Could not read Oracle demo schema spec [%s]" % e) raise e
def _getEulaAccepted(self, txn): txn.execute("SELECT value FROM config WHERE key = ?", ["eula-accepted"]) res = txn.fetchone() if res: eula_accepted = json_loads(res[0]) return eula_accepted else: raise Exception(URI_ERROR + "internal-error", "eula-accepted key not found.")
def _getAuthSecret(self, txn): txn.execute("SELECT value FROM config WHERE key = ?", ['admin-password']) res = txn.fetchone() if res: pw = str(json_loads(res[0])) return pw else: raise Exception(URI_ERROR + "internal-error", "Could not retrieve admin password from database.")
def _modifyConfig(self, txn, configDelta): ## determine actual set of modified stuff ## modified = {} for c in configDelta: txn.execute("SELECT value FROM config WHERE key = ?", [ c, ]) res = txn.fetchone() if res: cval = json_loads(res[0]) if cval != configDelta[c]: modified[c] = configDelta[c] txn.execute("UPDATE config SET value = ? WHERE key = ?", [json_dumps(configDelta[c]), c]) ## only something to do when there was an actual change ## if len(modified) > 0: ## recache config ## services = self.proto.factory.services if services.has_key("config"): services["config"].recache(txn) ## check if WebSocket option changed .. if so, notify WS factory ## wsOptionChanged = False for k in modified: if k[:2] == 'ws': wsOptionChanged = True break if wsOptionChanged: if self.proto.factory.services.has_key("appws"): self.proto.factory.services["appws"].setOptionsFromConfig() if self.proto.factory.services.has_key("echows"): self.proto.factory.services["echows"].setOptionsFromConfig( ) ## check for restart required ## for k in modified: if k in Database.SERVICES: self.proto.factory.issueRestartRequired() ## notify subscribers ## self.proto.dispatch(URI_EVENT + "on-config-modified", modified, [self.proto]) ## return modified set to caller ## return modified else: ## nothing changed ## return {}
def _getSingleConfig(self, txn, key): txn.execute("SELECT value FROM config WHERE key = ?", [key,]) res = txn.fetchone() if res: val = json_loads(res[0]) if key in Database.NETPORTS_TLS_KEYS and val is not None: val = self.proto.shrink(URI_SERVICEKEY + val) return val else: raise Exception(URI_ERROR + "invalid-config-parameter", "No configuration parameter '%s'" % key)
def _getAllConfig(self, txn): txn.execute("SELECT key, value FROM config ORDER BY key") res = {} for r in txn.fetchall(): key = r[0] val = json_loads(r[1]) if key in Database.NETPORTS_TLS_KEYS and val is not None: val = self.proto.shrink(URI_SERVICEKEY + val) res[key] = val return res
def _getAuthSecret(self, txn): txn.execute("SELECT value FROM config WHERE key = ?", ['admin-password']) res = txn.fetchone() if res: pw = str(json_loads(res[0])) return pw else: raise Exception( URI_ERROR + "internal-error", "Could not retrieve admin password from database.")
def _changePassword(self, txn, oldpassword, newpassword1, newpassword2): attrs = {"oldpassword": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN), "newpassword1": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN), "newpassword2": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN)} errcnt, errs = self.checkDictArg("user password", {"oldpassword": oldpassword, "newpassword1": newpassword1, "newpassword2": newpassword2}, attrs) if newpassword1 != newpassword2: errcnt += 1 if not errs.has_key('newpassword1') or errs.has_key('newpassword2'): p = 'newpassword1' else: p = 'newpassword2' if not errs.has_key(p): errs[p] = [] errs[p].append((self.shrink(URI_ERROR + "invalid-attribute-value"), "New password values do not match")) if errcnt: raise Exception(URI_ERROR + "illegal-argument", "one or more illegal arguments (%d errors)" % errcnt, errs) txn.execute("SELECT value FROM config WHERE key = ?", ['admin-password']) res = txn.fetchone() if res: pw = str(json_loads(res[0])) if pw == oldpassword: if newpassword1 != oldpassword: txn.execute("UPDATE config SET value = ? WHERE key = ?", [json_dumps(newpassword1), "admin-password"]) else: raise Exception(URI_ERROR + "illegal-argument", "one or more illegal arguments (%d errors)" % 2, {'newpassword1': [(self.shrink(URI_ERROR + "attribute-value-unchanged"), "Password unchanged")], 'newpassword2': [(self.shrink(URI_ERROR + "attribute-value-unchanged"), "Password unchanged")]}) else: raise Exception(URI_ERROR + "illegal-argument", "one or more illegal arguments (%d errors)" % 1, {'oldpassword': [(self.shrink(URI_ERROR + "invalid-attribute-value"), "Old password is invalid")]}) else: raise Exception(URI_ERROR + "internal-error", "Could not retrieve admin password from database.")
def _onRemoteCallResult(self, r, remotingRequest): """ Consume Ext.Direct remoting result. Note that this still can trigger a WAMP exception. """ try: res = json_loads(r) except Exception, e: raise Exception(URI_ERROR_REMOTING, "response payload could not be decoded", {'message': str(e), 'response': r, 'request': remotingRequest})
def _modifyConfig(self, txn, configDelta): ## determine actual set of modified stuff ## modified = {} for c in configDelta: txn.execute("SELECT value FROM config WHERE key = ?", [c,]) res = txn.fetchone() if res: cval = json_loads(res[0]) if cval != configDelta[c]: modified[c] = configDelta[c] txn.execute("UPDATE config SET value = ? WHERE key = ?", [json_dumps(configDelta[c]), c]) ## only something to do when there was an actual change ## if len(modified) > 0: ## recache config ## services = self.proto.factory.services if services.has_key("config"): services["config"].recache(txn) ## check if WebSocket option changed .. if so, notify WS factory ## wsOptionChanged = False for k in modified: if k[:2] == 'ws': wsOptionChanged = True break if wsOptionChanged: if self.proto.factory.services.has_key("appws"): self.proto.factory.services["appws"].setOptionsFromConfig() if self.proto.factory.services.has_key("echows"): self.proto.factory.services["echows"].setOptionsFromConfig() ## check for restart required ## for k in modified: if k in Database.SERVICES: self.proto.factory.issueRestartRequired() ## notify subscribers ## self.proto.dispatch(URI_EVENT + "on-config-modified", modified, [self.proto]) ## return modified set to caller ## return modified else: ## nothing changed ## return {}
def _getSingleConfig(self, txn, key): txn.execute("SELECT value FROM config WHERE key = ?", [ key, ]) res = txn.fetchone() if res: val = json_loads(res[0]) if key in Database.NETPORTS_TLS_KEYS and val is not None: val = self.proto.shrink(URI_SERVICEKEY + val) return val else: raise Exception(URI_ERROR + "invalid-config-parameter", "No configuration parameter '%s'" % key)
def loop(self): import time import pyodbc conn = pyodbc.connect(self.connectstr) cur1 = conn.cursor() cur2 = conn.cursor() cur1.execute( "SELECT COALESCE(MAX(id), 0), COALESCE(MIN(id), 0), COUNT(*) FROM crossbar.event" ) (id, minid, evtcnt) = cur1.fetchone() while not self.stopped: cur1.execute( "SELECT id, pushed_by, topic, payload_type, payload FROM crossbar.event WHERE id > ? ORDER BY id ASC", [id]) oldid = id for r in cur1.fetchall(): id = r[0] pushedBy = r[1] topic = r[2] payload = None if r[4] is not None: ## JSON payload if r[3] == 2: try: payload = json_loads(r[4]) except Exception, e: log.msg("%s - INVALID JSON PAYLOAD - %s" % (r[4], str(e))) ## plain string payload elif r[3] == 1: payload = r[4] else: log.msg("INVALID PAYLOAD TYPE %s" % r[4]) reactor.callFromThread(self.pusher, self.hanaconnectId, pushedBy, topic, payload) if self.purge and id > oldid: cur2.execute("DELETE FROM crossbar.event WHERE id <= ?", id) conn.commit() if self.throttle > 0: time.sleep(self.throttle)
def _setPassword(self, txn, password1, password2): txn.execute("SELECT value FROM config WHERE key = ?", ['admin-password']) res = txn.fetchone() if res: pw = json_loads(res[0]) if pw is not None: raise Exception((URI_ERROR + "invalid-invocation", "Initial password already set.")) else: raise Exception( URI_ERROR + "internal-error", "Could not retrieve admin password from database.") attrs = { "password1": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN), "password2": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN) } errcnt, errs = self.checkDictArg("user password", { "password1": password1, "password2": password2 }, attrs) if password1 != password2: errcnt += 1 if not errs.has_key('password1') or errs.has_key('password2'): p = 'password1' else: p = 'password2' if not errs.has_key(p): errs[p] = [] errs[p].append((self.shrink(URI_ERROR + "invalid-attribute-value"), "Passwords do not match")) if errcnt: raise Exception( URI_ERROR + "illegal-argument", "one or more illegal arguments (%d errors)" % errcnt, errs) txn.execute("UPDATE config SET value = ? WHERE key = ?", [json_dumps(password1), "admin-password"])
def _acceptEula(self, txn): txn.execute("SELECT value FROM config WHERE key = ?", ["eula-accepted"]) res = txn.fetchone() if res: eula_accepted = json_loads(res[0]) if eula_accepted: raise Exception(URI_ERROR + "illegal-invocation", "EULA already accepted.") else: now = utcnow() txn.execute("UPDATE config SET value = ? WHERE key = ?", [json_dumps(now), "eula-accepted"]) self.factory.services["config"].recache(txn) return now else: raise Exception(URI_ERROR + "internal-error", "EULA key not found.")
def loop(self): import time import pyodbc conn = pyodbc.connect(self.connectstr) cur1 = conn.cursor() cur2 = conn.cursor() cur1.execute("SELECT COALESCE(MAX(id), 0), COALESCE(MIN(id), 0), COUNT(*) FROM crossbar.event") (id, minid, evtcnt) = cur1.fetchone() while not self.stopped: cur1.execute( "SELECT id, pushed_by, topic, payload_type, payload FROM crossbar.event WHERE id > ? ORDER BY id ASC", [id], ) oldid = id for r in cur1.fetchall(): id = r[0] pushedBy = r[1] topic = r[2] payload = None if r[4] is not None: ## JSON payload if r[3] == 2: try: payload = json_loads(r[4]) except Exception, e: log.msg("%s - INVALID JSON PAYLOAD - %s" % (r[4], str(e))) ## plain string payload elif r[3] == 1: payload = r[4] else: log.msg("INVALID PAYLOAD TYPE %s" % r[4]) reactor.callFromThread(self.pusher, self.hanaconnectId, pushedBy, topic, payload) if self.purge and id > oldid: cur2.execute("DELETE FROM crossbar.event WHERE id <= ?", id) conn.commit() if self.throttle > 0: time.sleep(self.throttle)
def _getConfigChangeset(self, txn, attrs, delta): all = delta.copy() changed = {} ss = "" for p in attrs: ss += "'%s'," % p ss = ss[:-1] txn.execute("SELECT key, value FROM config WHERE key IN (" + ss + ")") for r in txn.fetchall(): val = json_loads(r[1]) if not all.has_key(r[0]): all[r[0]] = val else: if all[r[0]] != val: changed[r[0]] = all[r[0]] return (all, changed)
def _loadObject(self, txn, uri): ## check arguments ## if type(uri) not in [str, unicode]: raise Exception(URI_ERROR + "illegal-argument", "Expected type str/unicode for argument uri, but got %s" % str(type(uri))) ## resolve URI ## uri = self.proto.resolveOrPass(uri) txn.execute("SELECT obj FROM objstore WHERE uri = ?", [uri]) res = txn.fetchone() if res is not None: obj = json_loads(res[0]) return obj else: return None
def _storeObject(self, txn, uri, obj): ## check arguments ## if type(uri) not in [str, unicode]: raise Exception( URI_ERROR + "illegal-argument", "Expected type str/unicode for argument uri, but got %s" % str(type(uri))) ## resolve URI ## uri = self.proto.resolveOrPass(uri) modified = True txn.execute("SELECT obj FROM objstore WHERE uri = ?", [uri]) res = txn.fetchone() if res is not None: oldobj = json_loads(res[0]) if obj is not None: objser = json_dumps(obj) if objser != res[0]: txn.execute("UPDATE objstore SET obj = ? WHERE uri = ?", [objser, uri]) else: modified = False else: txn.execute("DELETE FROM objstore WHERE uri = ?", [uri]) else: oldobj = None if obj is not None: objser = json_dumps(obj) txn.execute("INSERT INTO objstore (uri, obj) VALUES (?, ?)", [uri, objser]) else: modified = False if modified: event = {'uri': uri, 'old': oldobj, 'new': obj} self.proto.dispatch(URI_EVENT + "on-store-modified", event, [self.proto]) event["uri"] = self.proto.shrink(uri) return event else: return None
def _loadObject(self, txn, uri): ## check arguments ## if type(uri) not in [str, unicode]: raise Exception( URI_ERROR + "illegal-argument", "Expected type str/unicode for argument uri, but got %s" % str(type(uri))) ## resolve URI ## uri = self.proto.resolveOrPass(uri) txn.execute("SELECT obj FROM objstore WHERE uri = ?", [uri]) res = txn.fetchone() if res is not None: obj = json_loads(res[0]) return obj else: return None
def _storeObject(self, txn, uri, obj): ## check arguments ## if type(uri) not in [str, unicode]: raise Exception(URI_ERROR + "illegal-argument", "Expected type str/unicode for argument uri, but got %s" % str(type(uri))) ## resolve URI ## uri = self.proto.resolveOrPass(uri) modified = True txn.execute("SELECT obj FROM objstore WHERE uri = ?", [uri]) res = txn.fetchone() if res is not None: oldobj = json_loads(res[0]) if obj is not None: objser = json_dumps(obj) if objser != res[0]: txn.execute("UPDATE objstore SET obj = ? WHERE uri = ?", [objser, uri]) else: modified = False else: txn.execute("DELETE FROM objstore WHERE uri = ?", [uri]) else: oldobj = None if obj is not None: objser = json_dumps(obj) txn.execute("INSERT INTO objstore (uri, obj) VALUES (?, ?)", [uri, objser]) else: modified = False if modified: event = {'uri': uri, 'old': oldobj, 'new': obj} self.proto.dispatch(URI_EVENT + "on-store-modified", event, [self.proto]) event["uri"] = self.proto.shrink(uri) return event else: return None
def _setPassword(self, txn, password1, password2): txn.execute("SELECT value FROM config WHERE key = ?", ['admin-password']) res = txn.fetchone() if res: pw = json_loads(res[0]) if pw is not None: raise Exception((URI_ERROR + "invalid-invocation", "Initial password already set.")) else: raise Exception(URI_ERROR + "internal-error", "Could not retrieve admin password from database.") attrs = {"password1": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN), "password2": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN)} errcnt, errs = self.checkDictArg("user password", {"password1": password1, "password2": password2}, attrs) if password1 != password2: errcnt += 1 if not errs.has_key('password1') or errs.has_key('password2'): p = 'password1' else: p = 'password2' if not errs.has_key(p): errs[p] = [] errs[p].append((self.shrink(URI_ERROR + "invalid-attribute-value"), "Passwords do not match")) if errcnt: raise Exception(URI_ERROR + "illegal-argument", "one or more illegal arguments (%d errors)" % errcnt, errs) txn.execute("UPDATE config SET value = ? WHERE key = ?", [json_dumps(password1), "admin-password"])
def _login(self, txn, authResponse, stayLoggedIn): if self.authChallenge is None: raise Exception(URI_ERROR + "login-without-previous-request", "Login attempt without previous login request.") txn.execute("SELECT value FROM config WHERE key = ?", ['admin-password']) res = txn.fetchone() if res: pw = str(json_loads(res[0])) h = hmac.new(pw, self.authChallenge, hashlib.sha256) v = binascii.b2a_base64(h.digest()).strip() if v == str(authResponse): self.authUser = self.authChallengeUser if stayLoggedIn: cookie = ''.join([ random.choice( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" ) for i in xrange(64) ]) now = utcnow() txn.execute( "INSERT INTO cookie (created, username, value) VALUES (?, ?, ?)", [now, "admin", cookie]) self.authCookie = cookie res = cookie else: res = None self.onLogin() return res else: raise Exception(URI_ERROR + "login-failed", "Login failed.") else: raise Exception( URI_ERROR + "internal-error", "Could not retrieve admin password from database.")
offset = error.offset message = error.message context = error.context ## handle crossbar.io application errors ## if code == 20999: ## extract crossbar.io application error payload ## try: lines = message.splitlines() fl = lines[0] i = fl.find(':') + 2 s = fl[i:].strip() o = json_loads(s) rest = '\n'.join(lines[1:]) except: o = {} rest = message if o.has_key('uri'): uri = o['uri'] else: uri = URI_ERROR_SQL + ("%d" % code) if o.has_key('desc'): #m = "%s\n%s" % (o['desc'], rest) m = o['desc'] else: m = rest
def getApplianceVersion(self): try: o = json_loads(open("/etc/appliance.json").read()) return o["image"] except: return "unknown"
def render_POST(self, request): """ The HTTP/POST to WebSockets hub Twisted/Web resource main method. """ try: path = request.path args = request.args headers = request.getAllHeaders() if headers.get("content-type", "missing") != 'application/x-www-form-urlencoded': return self.deny( request, 400, "bad or missing content type ('%s')" % headers.get("content-type", "missing")) ## FIXME: post-body-limit ## content_length = int(headers.get("content-length", 0)) if content_length > self._getContentLengthLimit(): return self.deny( request, 400, "content length (%d) exceeds maximum (%d)" % (content_length, self._getContentLengthLimit())) if not args.has_key("topic"): return self.deny(request, 400, "missing query parameter 'topic'") topic = args["topic"][0] appkey = args.get("appkey", [False])[0] signature = args.get("signature", [False])[0] timestamp_str = args.get("timestamp", [False])[0] if appkey or signature or timestamp_str: if not (appkey and signature and timestamp_str): return self.deny( request, 400, "either all or none of parameters 'appkey', 'signature' and 'timestamp' must be present" ) if timestamp_str: # '2011-10-14T12:59:51Z' timestamp = parseutc(timestamp_str) if timestamp is None: return self.deny( request, 400, "invalid timestamp '%s' (must be i.e. '2011-10-14T16:59:51Z'" % timestamp_str) delta = int( round( abs(timestamp - datetime.datetime.utcnow()).total_seconds())) if delta > self._getTimestampDeltaLimit(): return self.deny( request, 400, "timestamp expired (delta %d seconds)" % delta) else: timestamp = None if args.has_key('event'): json_str = args['event'][0] else: json_str = request.content.read() if appkey: sig = self.services["restpusher"].signature( topic, appkey, timestamp_str, json_str) if sig is None: return self.deny(request, 400, "unknown application key '%s'" % appkey) if sig != signature: return self.deny( request, 401, "invalid request signature (expected %s, got %s)" % (sig, signature)) user_agent = headers.get("user-agent", "unknown") client_ip = request.getClientIP() is_secure = request.isSecure() auth = self.services["restpusher"].authorize( topic, client_ip, appkey) if auth[0]: try: event = json_loads(json_str) except: return self.deny(request, 400, "invalid JSON in request body") if args.has_key("exclude"): exclude = [ x.strip() for x in args["exclude"][0].split(",") ] else: exclude = [] if args.has_key("eligible"): eligible = [ x.strip() for x in args["eligible"][0].split(",") ] else: eligible = None ## dispatch & log event d = self.services["appws"].dispatchHubEvent( topic, event, exclude, eligible) d.addCallback(lambda res: self.log(user_agent, client_ip, is_secure, topic, content_length, postrule_id=auth[1], receiver_count=res[0], requested_count=res[1])) self.stats['publish-allowed'] += 1 self.statsChanged = True ## signal success to submitter request.setResponseCode(202) return "" else: return self.deny( request, 401, "not authorized (denied by post rule %s: %s)" % (auth[1], auth[2])) except Exception, e: ## catch all .. should not happen (usually) return self.deny(request, 500, "internal server error ('%s')" % str(e))
def _cacheConfig(self, res): self._config = {} for r in res: self._config[r[0]] = json_loads(r[1]) log.msg("Config._cacheConfig (%d)" % len(self._config))
class OraPushClient(DbPushClient): """ Oracle Push Client. """ LOGID = "OraPushClient" def loop(self): ## use DBMS_PIPE based notification self.usePipe = True ## DBMS_PIPE.receive_message timeout in secs self.pipeTimeout = int(1) ## Iff true, recheck event table on every pipe timeout self.recheckOnPipeTimeout = False import time, json import os os.environ["NLS_LANG"] = "AMERICAN_AMERICA.UTF8" import cx_Oracle try: dsn = cx_Oracle.makedsn(self.connect.host, self.connect.port, self.connect.sid) self.conn = cx_Oracle.connect(self.connect.user, self.connect.password, dsn, threaded = False) self.conn.autocommit = True log.msg("pusher Oracle connection established") except Exception, e: #log.msg(str(e)) raise e ## verify that we are the only pusher connected to this DB ## by allocating an X-lock. ## FIXME: ## - the lock is not schema-wise, but instance wise ## - we should cover the whole crossbar.io/Oracle integration (not only the pusher) cur = self.conn.cursor() cur.prepare(""" DECLARE l_lockhandle VARCHAR2(128); BEGIN DBMS_LOCK.ALLOCATE_UNIQUE('crossbar', l_lockhandle); :retval := DBMS_LOCK.REQUEST(l_lockhandle, DBMS_LOCK.X_MODE, 0, FALSE); END; """) curVars = cur.setinputsizes(retval = cx_Oracle.NUMBER) cur.execute(None) retval = int(curVars["retval"].getvalue()) if retval != 0: rmap = {0: "Success", 1: "Timeout", 2: "Deadlock", 3: "Parameter error", 4: "Don't own lock specified by id or lockhandle", 5: "Illegal lock handle"} raise Exception("There seems to be another pusher connected [lock result %d - %s]" % (retval, rmap.get(retval, "??"))) else: log.msg("Ok, we are the only pusher connected to the Oracle instance") self.isConnected = True self.pusher.publishPusherStateChange(self.connect.id, True, True) cur = self.conn.cursor() cur.execute("SELECT sys_context('USERENV', 'SESSION_USER') FROM dual") session_user = cur.fetchone()[0].upper() cur.execute(""" BEGIN DBMS_SESSION.SET_IDENTIFIER(:1); END; """, ['CROSSBAR_%s_PUSHER_%s' % (session_user, self.connect.id)]) ## when using DBMS_PIPE, the name of the event pipe PIPENAME = 'CROSSBAR_%s_ONPUBLISH' % session_user print "using pipe", PIPENAME ## if using pipe mode, prepare for that ## if self.usePipe: cur.execute(""" BEGIN SYS.DBMS_PIPE.purge(:pipename); END; """, pipename = PIPENAME) curWaitPipe = self.conn.cursor() curWaitPipe.prepare(""" BEGIN :retval := SYS.DBMS_PIPE.receive_message(:pipename, :timeout); IF :retval = 0 THEN SYS.DBMS_PIPE.unpack_message(:event_id); SYS.DBMS_PIPE.purge(:pipename); END IF; END; """) curWaitPipeVars = curWaitPipe.setinputsizes(retval = cx_Oracle.NUMBER, pipename = cx_Oracle.STRING, timeout = cx_Oracle.NUMBER, event_id = cx_Oracle.NUMBER) pipePipename = curWaitPipeVars["pipename"] pipePipename.setvalue(0, PIPENAME) pipeTimeout = curWaitPipeVars["timeout"] pipeTimeout.setvalue(0, self.pipeTimeout) pipeRetVal = curWaitPipeVars["retval"] pipeEventId = curWaitPipeVars["event_id"] ## setup stuff for getting unprocessed events ## curGetEvents = self.conn.cursor() curGetEvents.prepare(""" SELECT id, published_by, topic, qos, payload_type, payload_str, payload_lob, exclude_sids, eligible_sids FROM event WHERE id > :id AND processed_at IS NULL ORDER BY id ASC """) curGetEventsVars = curGetEvents.setinputsizes(id = cx_Oracle.NUMBER) getFromEventId = curGetEventsVars["id"] ## setup stuff for purging/marking processed events ## if self.purge: curDeleteEvents = self.conn.cursor() curDeleteEvents.prepare("""DELETE FROM event WHERE id = :id""") curDeleteEventsVars = curDeleteEvents.setinputsizes(id = cx_Oracle.NUMBER) deleteEventId = curDeleteEventsVars["id"] else: curMarkEvents = self.conn.cursor() curMarkEvents.prepare(""" UPDATE event SET processed_at = systimestamp at time zone 'utc', processed_status = :processed_status, processed_errmsg = :processed_errmsg, processed_len = :processed_len WHERE id = :id""") curMarkEventsVars = curMarkEvents.setinputsizes(id = cx_Oracle.NUMBER, processed_status = cx_Oracle.NUMBER, processed_errmsg = cx_Oracle.STRING, processed_len = cx_Oracle.NUMBER) markEventId = curMarkEventsVars["id"] markEventProcessedStatus = curMarkEventsVars["processed_status"] markEventProcessedErrMsg = curMarkEventsVars["processed_errmsg"] markEventProcessedLen = curMarkEventsVars["processed_len"] ## setup stuff for tracking stats of processed events ## if self._trackDispatched: curTrackEvents = self.conn.cursor() curTrackEvents.prepare(""" UPDATE event SET dispatch_status = :dispatch_status, dispatch_success = :dispatch_success, dispatch_failed = :dispatch_failed WHERE id = :id""") curTrackEventsVars = curTrackEvents.setinputsizes(id = cx_Oracle.NUMBER, dispatch_status = cx_Oracle.NUMBER, dispatch_success = cx_Oracle.NUMBER, dispatch_failed = cx_Oracle.NUMBER) trackEventId = curTrackEventsVars["id"] trackDispatchStatus = curTrackEventsVars["dispatch_status"] trackDispatchSuccess = curTrackEventsVars["dispatch_success"] trackDispatchFailed = curTrackEventsVars["dispatch_failed"] ## check current contents of event table ## cur.execute("SELECT NVL(MIN(id), 0), NVL(MAX(id), 0), COUNT(*) FROM event WHERE processed_at IS NULL") (minId, maxId, evtCnt) = cur.fetchone() ## event ID after we will start processing events .. ## id = None ## purge or mark events left over since we were offline ## if evtCnt > 0: if not self.processOldEvents: log.msg("skipping %d events accumulated since we were offline [purge = %s]" % (evtCnt, self.purge)) if self.purge: cur.execute(""" DELETE FROM event WHERE id >= :1 AND id <= :2 AND processed_at IS NULL""", [minId, maxId]) #self.conn.commit() else: processed_status = 5 processed_errmsg = None cur.execute(""" UPDATE event SET processed_at = systimestamp at time zone 'utc', processed_status = :1, processed_errmsg = :2 WHERE id >= :3 AND id <= :4 AND processed_at IS NULL""", [processed_status, processed_errmsg[:4000] if processed_errmsg is not None else None, minId, maxId]) #self.conn.commit() ## start processing only with new events (skipping events that accumulated while we were offline) id = maxId else: log.msg("%d events accumulated since we were offline - will process those now" % evtCnt) ## start processing at events that accumulated while we were offline id = minId - 1 else: log.msg("no events accumulated since we were offline") ## start processing with new events id = maxId ## inner loop, will run until we get stop()'ed ## while not self.stopped: checkAgain = True while checkAgain: if self.trackDispatched: while self.dispatched and len(self.dispatched) > 0: trackedEventId, dispatch_status, dispatch_success, dispatch_failed = self.dispatched.popleft() trackEventId.setvalue(0, trackedEventId) trackDispatchStatus.setvalue(0, dispatch_status) trackDispatchSuccess.setvalue(0, dispatch_success) trackDispatchFailed.setvalue(0, dispatch_failed) curTrackEvents.execute(None) #self.conn.commit() ## we do this rather low-level with a cursor loop, since ## we process CLOB columns ## gotRows = 0 #cur.execute("""SELECT id, # published_by, # topic, # qos, # payload_type, # payload_str, # payload_lob, # exclude_sids, # eligible_sids # FROM # event WHERE id > :1 AND processed_at IS NULL # ORDER BY id ASC""", [id]) getFromEventId.setvalue(0, id) curGetEvents.execute(None) doFetch = True while doFetch: #r = cur.fetchone() r = curGetEvents.fetchone() if r is not None: gotRows += 1 ## processed_status: ## ## 0 - ok ## 1 - invalid event topic URI ## 2 - illegal event payload type ## 3 - invalid event payload ## 4 - illegal event qos type ## 5 - unprocessed old event (got published as we were offline) ## processed_status = 0 # default to valid processing processed_errmsg = None # filled when processed_status != 0 id = r[0] pushedBy = r[1] topic = r[2] qos = r[3] payload_type = r[4] if r[6] is not None: ## cx_Oracle.LOB object payload_raw_len = r[6].size() payload_raw = r[6].read() else: ## cx_Oracle.STRING if r[5] is not None: payload_raw_len = len(r[5]) payload_raw = r[5] else: payload_raw_len = 0 payload_raw = None payload = None exclude = r[7] if r[7] is not None else [] eligible = r[8] if r[8] is not None else None ## validate/parse event ## if processed_status == 0: uriValid, result = validateUri(topic) if not uriValid: processed_status = 1 processed_errmsg = result if processed_status == 0: if payload_type == 1: ## plain string ## payload = payload_raw elif payload_type == 2: ## JSON ## try: if payload_raw is not None: payload = json_loads(payload_raw) else: payload = None except Exception, e: ## this should not happen, since we have serialized the ## payload from a JSON typed object within Oracle! ## processed_status = 3 # invalid payload processed_errmsg = "invalid JSON payload ('%s')" % str(e) else: ## this should not happen, since we have a CHECK constraint ## on the underlying event table! ## processed_status = 2 processed_errmsg = "illegal event payload type %d" % payload_type if processed_status == 0: if qos == 1: pass else: processed_status = 4 processed_errmsg = "illegal event qos type %d" % qos ## let the events be dispatched on the reactor thread ## if processed_status == 0: self.reactor.callFromThread(self.pusher.push, id, self.connect.id, pushedBy, topic, payload, exclude, eligible) ## purge or mark processed event ## if self.purge: #cur.execute("DELETE FROM event WHERE id = :1", [id]) deleteEventId.setvalue(0, id) curDeleteEvents.execute(None) #self.conn.commit() else: #cur.execute("UPDATE event SET processed_at = systimestamp at time zone 'utc', processed_status = :1, processed_errmsg = :2 WHERE id = :3", [processed_status, processed_errmsg[:4000] if processed_errmsg is not None else None, id]) markEventId.setvalue(0, id) markEventProcessedStatus.setvalue(0, processed_status) markEventProcessedErrMsg.setvalue(0, processed_errmsg) markEventProcessedLen.setvalue(0, payload_raw_len) curMarkEvents.execute(None) #self.conn.commit() else: doFetch = False ## immediately check again if we got rows. ## otherwise go to sleep / wait on pipe notification ## if gotRows > 0: #log.msg("got %d events, checking again .." % gotRows) checkAgain = True else: #log.msg("no new events") checkAgain = False ## wait for new events .. ## if not self.usePipe: ## when in polling mode, just sleep a little and recheck for events .. ## if self.pollThrottle > 0: time.sleep(self.pollThrottle) else: ## when using DBMS_PIPE, block in pipe receive for new events .. ## waitForPipe = True debugPipe = False while waitForPipe: ## the following will block in pipe receive .. ## curWaitPipe.execute(None) retval = int(pipeRetVal.getvalue()) if retval == 1: ## pipe timeout ## if debugPipe: log.msg("DBMS_PIPE timeout") elif retval == 0: ## the pipe got a new event ID .. ## notifiedId = int(pipeEventId.getvalue()) waitForPipe = False if debugPipe: log.msg("DBMS_PIPE data available [event %s]" % notifiedId) else: ## could not receive from pipe .. something bad happened. ## exit and rely on automatic pusher restart ## raise Exception("error while doing DBMS_PIPE.receive_message - return value %d" % retval) if self.recheckOnPipeTimeout: waitForPipe = False
def render_POST(self, request): """ The HTTP/POST to WebSockets hub Twisted/Web resource main method. """ try: path = request.path args = request.args headers = request.getAllHeaders() if headers.get("content-type", "missing") != "application/x-www-form-urlencoded": return self.deny( request, 400, "bad or missing content type ('%s')" % headers.get("content-type", "missing") ) ## FIXME: post-body-limit ## content_length = int(headers.get("content-length", 0)) if content_length > self._getContentLengthLimit(): return self.deny( request, 400, "content length (%d) exceeds maximum (%d)" % (content_length, self._getContentLengthLimit()), ) if not args.has_key("topicuri"): return self.deny(request, 400, "missing query parameter 'topicuri'") topicuri = args["topicuri"][0] appkey = args.get("appkey", [False])[0] signature = args.get("signature", [False])[0] timestamp_str = args.get("timestamp", [False])[0] if appkey or signature or timestamp_str: if not (appkey and signature and timestamp_str): return self.deny( request, 400, "either all or none of parameters 'appkey', 'signature' and 'timestamp' must be present", ) if timestamp_str: # '2011-10-14T12:59:51Z' timestamp = parseutc(timestamp_str) if timestamp is None: return self.deny( request, 400, "invalid timestamp '%s' (must be i.e. '2011-10-14T16:59:51Z'" % timestamp_str ) delta = int(round(abs(timestamp - datetime.datetime.utcnow()).total_seconds())) if delta > self._getTimestampDeltaLimit(): return self.deny(request, 400, "timestamp expired (delta %d seconds)" % delta) else: timestamp = None if args.has_key("body"): json_str = args["body"][0] else: json_str = request.content.read() if appkey: sig = self.services["restpusher"].signature(topicuri, appkey, timestamp_str, json_str) if sig is None: return self.deny(request, 400, "unknown application key '%s'" % appkey) if sig != signature: return self.deny(request, 401, "invalid request signature (expected %s, got %s)" % (sig, signature)) user_agent = headers.get("user-agent", "unknown") client_ip = request.getClientIP() is_secure = request.isSecure() auth = self.services["restpusher"].authorize(topicuri, client_ip, appkey) if auth[0]: try: event = json_loads(json_str) except: return self.deny(request, 400, "invalid JSON in request body") if args.has_key("exclude"): exclude = [x.strip() for x in args["exclude"][0].split(",")] else: exclude = [] if args.has_key("eligible"): eligible = [x.strip() for x in args["eligible"][0].split(",")] else: eligible = None ## dispatch & log event d = self.services["appws"].dispatchHubEvent(topicuri, event, exclude, eligible) d.addCallback( lambda res: self.log( user_agent, client_ip, is_secure, topicuri, content_length, postrule_id=auth[1], receiver_count=res[0], requested_count=res[1], ) ) self.stats["publish-allowed"] += 1 self.statsChanged = True ## signal success to submitter request.setResponseCode(202) return "" else: return self.deny(request, 401, "not authorized (denied by post rule %s: %s)" % (auth[1], auth[2])) except Exception, e: ## catch all .. should not happen (usually) return self.deny(request, 500, "internal server error ('%s')" % str(e))
def _changePassword(self, txn, oldpassword, newpassword1, newpassword2): attrs = { "oldpassword": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN), "newpassword1": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN), "newpassword2": (True, [str, unicode], AdminWebSocketProtocol.USER_PASSWORD_MIN_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_MAX_LENGTH, AdminWebSocketProtocol.USER_PASSWORD_PATTERN) } errcnt, errs = self.checkDictArg( "user password", { "oldpassword": oldpassword, "newpassword1": newpassword1, "newpassword2": newpassword2 }, attrs) if newpassword1 != newpassword2: errcnt += 1 if not errs.has_key('newpassword1') or errs.has_key( 'newpassword2'): p = 'newpassword1' else: p = 'newpassword2' if not errs.has_key(p): errs[p] = [] errs[p].append((self.shrink(URI_ERROR + "invalid-attribute-value"), "New password values do not match")) if errcnt: raise Exception( URI_ERROR + "illegal-argument", "one or more illegal arguments (%d errors)" % errcnt, errs) txn.execute("SELECT value FROM config WHERE key = ?", ['admin-password']) res = txn.fetchone() if res: pw = str(json_loads(res[0])) if pw == oldpassword: if newpassword1 != oldpassword: txn.execute("UPDATE config SET value = ? WHERE key = ?", [json_dumps(newpassword1), "admin-password"]) else: raise Exception( URI_ERROR + "illegal-argument", "one or more illegal arguments (%d errors)" % 2, { 'newpassword1': [(self.shrink(URI_ERROR + "attribute-value-unchanged"), "Password unchanged")], 'newpassword2': [ (self.shrink(URI_ERROR + "attribute-value-unchanged"), "Password unchanged") ] }) else: raise Exception( URI_ERROR + "illegal-argument", "one or more illegal arguments (%d errors)" % 1, { 'oldpassword': [(self.shrink(URI_ERROR + "invalid-attribute-value"), "Old password is invalid")] }) else: raise Exception( URI_ERROR + "internal-error", "Could not retrieve admin password from database.")