def build_authcheck(request, ti='', forceprivate=False, action=None): """Build an SQL WHERE clause which enforces access restrictions. Will pull any credentials out of the request object passed in. "action" should either be None, in which case the subscription permissions will be used, or one of the can_XXX permissions in the permission table. """ query = "sub.id = s.subscription_id AND " if not 'private' in request.args and not forceprivate: query += "(sub%s.public " % ti else: query += "(false " if 'key' in request.args: query += 'OR ( (' + ' OR '.join(["sub.key = %s" % escape_string(x + ti) for x in request.args['key']]) + \ ') )' if (settings.conf['features']['permissions'] and 'key' in request.args and action is not None): # add permissions granted by the permissions table query += """OR ( (sub.id IN (SELECT perm_sub.subscription_id FROM permission perm, permission_subscriptions perm_sub WHERE perm.id = perm_sub.permission_id AND ((perm.valid_after IS NULL OR perm.valid_after < current_timestamp) AND (perm.valid_until IS NULL or perm.valid_until > current_timestamp)) AND (""" + \ ' OR '.join(('perm.key = %s' % escape_string(x + ti) for x in request.args['key'])) + \ (') AND perm.can_%s IS true)' % action) + \ ') AND sub.id = s.subscription_id)' query += ")" return query
def build_setstring(setvals, wherevals): # set tags regex = None regex_tag = None for stmt in wherevals: if stmt.op == ast.Statement.OP_REGEX: if regex == None: regex = stmt else: raise qg.QueryException("Too many regexes in set. Only one supported!") if regex == None: new_tags = ' || '.join(map(lambda (t,v): "hstore(%s, %s)" % (escape_string(t), escape_string(v)), setvals)) else: new_tags = ' || '.join(map(lambda (t,v): "hstore(%s, regexp_replace(metadata -> %s, '^.*%s.*$', %s))" % (escape_string(t), regex.args[0], regex.args[1][1:-1].replace('\\\\', '\\'), escape_string(v).replace('\\\\', '\\')), setvals)) regex_tag = regex.args[0][1:-1] return new_tags, regex_tag
def build_setstring(setvals, wherevals): # set tags regex = None for stmt in wherevals: if stmt.op == ast.Statement.OP_REGEX: if regex == None: regex = stmt else: raise qg.QueryException( "Too many regexes in set. Only one supported!") if regex == None: new_tags = ' || '.join( map( lambda (t, v): "hstore(%s, %s)" % (escape_string(t), escape_string(v)), setvals)) else: new_tags = ' || '.join( map( lambda (t, v): "hstore(%s, regexp_replace(metadata -> %s, '^.*%s.*$', %s))" % (escape_string(t), regex.args[0], regex.args[1][1:-1].replace( '\\\\', '\\'), escape_string(v).replace('\\\\', '\\')), setvals)) return new_tags
def build_authcheck(request, ti='', forceprivate=False, action=None): """Build an SQL WHERE clause which enforces access restrictions. Will pull any credentials out of the request object passed in. "action" should either be None, in which case the subscription permissions will be used, or one of the can_XXX permissions in the permission table. """ if not 'private' in request.args and not forceprivate: query = "(sub%s.public " % ti else: query = "(false " if 'key' in request.args: query += 'OR ( (' + ' OR '.join(["sub.key = %s" % escape_string(x + ti) for x in request.args['key']]) + \ ') AND sub.id = s.subscription_id) ' if (settings.conf['features']['permissions'] and 'key' in request.args and action is not None): # add permissions granted by the permissions table query += """OR ( (sub.id IN (SELECT perm_sub.subscription_id FROM permission perm, permission_subscriptions perm_sub WHERE perm.id = perm_sub.permission_id AND ((perm.valid_after IS NULL OR perm.valid_after < current_timestamp) AND (perm.valid_until IS NULL or perm.valid_until > current_timestamp)) AND (""" + \ ' OR '.join(('perm.key = %s' % escape_string(x + ti) for x in request.args['key'])) + \ (') AND perm.can_%s IS true)' % action) + \ ') AND sub.id = s.subscription_id)' query += ")" return query
def add_formula_restrictions(c_restrict, f_restrict): extra = [] for k, v in SetDict(f_restrict): if k != "uuid": extra.append("((s.metadata -> %s) ~ %s)" % (escape_string(k), escape_string(v))) else: extra.append("(s.uuid = %s)" % escape_string(v)) if len(extra): return c_restrict + " AND (" + " OR ".join(extra) + ")" else: return c_restrict
def add_formula_restrictions(c_restrict, f_restrict): extra = [] for k, v in SetDict(f_restrict): if k != 'uuid': extra.append('((s.metadata -> %s) ~ %s)' % (escape_string(k), escape_string(v))) else: extra.append('(s.uuid = %s)' % escape_string(v)) if len(extra): return c_restrict + ' AND (' + ' OR '.join(extra) + ')' else: return c_restrict
def p_selector(t): """selector : tag_list | '*' | DISTINCT LVALUE | DISTINCT """ if t[1] == "distinct": if len(t) == 2: t[0] = selector("DISTINCT skeys(s.metadata)", ext_non_null) elif t[2] == "uuid": t[0] = selector("DISTINCT s.uuid", ext_non_null) else: t[0] = selector("DISTINCT (s.metadata -> %s)" % escape_string(t[2]), ext_non_null) else: t[0] = make_tag_select(t[1])
def p_selector(t): """selector : tag_list | '*' | DISTINCT LVALUE | DISTINCT """ if t[1] == 'distinct': if len(t) == 2: t[0] = selector("DISTINCT skeys(s.metadata)", ext_non_null) elif t[2] == 'uuid': t[0] = selector("DISTINCT s.uuid", ext_non_null) else: t[0] = selector("DISTINCT (s.metadata -> %s)" % escape_string(t[2]), ext_non_null) else: t[0] = make_tag_select(t[1])
def p_statement_binary(t): """statement_binary : LVALUE '=' QSTRING | LVALUE LIKE QSTRING | LVALUE TILDE QSTRING """ if t[1] == 'uuid': t[0] = 's.uuid %s %s' % (t[2], escape_string(t[3])) else: if t[2] == '=': q = "s.metadata @> hstore(%s, %s)" % (escape_string(t[1]), escape_string(t[3])) elif t[2] == 'like': q = "(s.metadata -> %s) LIKE %s" % (escape_string(t[1]), escape_string(t[3])) elif t[2] == '~': q = "(s.metadata -> %s) ~ %s" % (escape_string(t[1]), escape_string(t[3])) t[0] = '(s.metadata ? %s) AND (%s)' % (escape_string(t[1]), q)
def make_set_rv(t): # build the query for a set expression if len(t) > 3: # if there is a where clause, grab it where_clause = t[4] else: # if no where clause, just do something fast that matches # everything where_clause = ast.Statement(ast.Statement.OP_UUID) noop = "noop" in t.parser.request.args # build a list of what tags we're updating, as well as the # name of the regex tag. We're currently not smart enough to # deal with more than one regex match in a set statement, due # to issues with back references. new_tags, regex_tag = build_setstring(t[2], where_clause) tag_list = [v[0] for v in t[2]] if regex_tag: tag_list.append(regex_tag) # if we're in noop mode, we need to tweak the SQL if not noop: command = "UPDATE stream SET metadata = metadata || " from_stmt = "" else: command = "SELECT uuid, " from_stmt = "FROM stream" # build the first part of the query that depends on if this is a noop q = "%(command)s %(hstore_expression)s %(from_stmt)s WHERE ID IN" % { "command": command, "hstore_expression": new_tags, "from_stmt": from_stmt } # the where caluse always looks the same q += "(SELECT s.id FROM stream s, subscription sub " + \ "WHERE (" + where_clause.render() + ") AND s.subscription_id = sub.id AND " + \ qg.build_authcheck(t.parser.request, forceprivate=True) + ") " if not noop: # return the list of modifications. if this was a noop we # get this for free. q += " RETURNING uuid, slice(metadata, ARRAY[" + \ ','.join((escape_string(v) for v in tag_list)) + "])" return lambda x: ext_set(tag_list, x), q
def make_set_rv(t): # build the query for a set expression if len(t) > 3: # if there is a where clause, grab it where_clause = t[4] else: # if no where clause, just do something fast that matches # everything where_clause = ast.Statement(ast.Statement.OP_UUID) noop = "noop" in t.parser.request.args # build a list of what tags we're updating, as well as the # name of the regex tag. We're currently not smart enough to # deal with more than one regex match in a set statement, due # to issues with back references. new_tags, regex_tag = build_setstring(t[2], where_clause) tag_list = [v[0] for v in t[2]] if regex_tag: tag_list.append(regex_tag) # if we're in noop mode, we need to tweak the SQL if not noop: command = "UPDATE stream SET metadata = metadata || " from_stmt = "" else: command = "SELECT uuid, " from_stmt = "FROM stream" # build the first part of the query that depends on if this is a noop q = "%(command)s %(hstore_expression)s %(from_stmt)s WHERE ID IN" % { "command": command, "hstore_expression": new_tags, "from_stmt": from_stmt } # the where caluse always looks the same q += "(SELECT s.id FROM stream s, subscription sub " + \ "WHERE (" + where_clause.render() + ") AND s.subscription_id = sub.id AND " + \ qg.build_authcheck(t.parser.request, forceprivate=True, action='set') + ") " if not noop: # return the list of modifications. if this was a noop we # get this for free. q += " RETURNING uuid, slice(metadata, ARRAY[" + \ ','.join((escape_string(v) for v in tag_list)) + "])" return lambda x: ext_set(tag_list, x), q
def make_clause(x): if x == 'uuid': return "(s.uuid)" else: return "(s.metadata -> %s)" % escape_string(x)
def p_statement_unary(t): """statement_unary : HAS LVALUE""" if t[1] == 'has': t[0] = 's.metadata ? %s' % escape_string(t[2])
def p_query(t): """query : SELECT selector WHERE statement | SELECT selector | SELECT data_clause WHERE statement | DELETE tag_list WHERE statement | DELETE WHERE statement | SET set_list WHERE statement | APPLY apply_statement | HELP | HELP LVALUE """ if t[1] == 'select': if len(t) == 5: t[0] = make_select_rv(t, t[2], t[4]) elif len(t) == 3: t[0] = make_select_rv(t, t[2]) elif t[1] == 'delete': # a new delete inner statement enforces that we only delete # things which we have the key for. if t[2] == 'where': # delete the whole stream, gone. this also deletes the # data in the backend readingdb. t[0] = ext_deletor, \ """DELETE FROM stream WHERE id IN ( SELECT s.id FROM stream s, subscription sub WHERE (%(restrict)s) AND s.subscription_id = sub.id AND (%(auth)s) ) RETURNING id, uuid """ % { 'restrict': t[3], 'auth': qg.build_authcheck(t.parser.request, forceprivate=True) } else: # this alters the tags but doesn't touch the data del_tags = ', '.join(map(escape_string, t[2])) q = "UPDATE stream SET metadata = metadata - ARRAY[" + del_tags + \ "] WHERE id IN " + \ "(SELECT s.id FROM stream s, subscription sub " + \ "WHERE (" + t[4] + ") AND s.subscription_id = sub.id AND " + \ qg.build_authcheck(t.parser.request, forceprivate=True) + ")" t[0] = None, q elif t[1] == 'set': # set tags new_tags = ' || '.join(map(lambda (t,v): "hstore(%s, %s)" % (escape_string(t), escape_string(v)), t[2])) q = "UPDATE stream SET metadata = metadata || " + new_tags + \ " WHERE id IN " + \ "(SELECT s.id FROM stream s, subscription sub " + \ "WHERE (" + t[4] + ") AND s.subscription_id = sub.id AND " + \ qg.build_authcheck(t.parser.request, forceprivate=True) + ")" t[0] = None, q elif t[1] == 'apply': t[0] = t[2] elif t[1] == 'help': if len(t) == 2: t[0] = help.help(), None else: t[0] = help.help(t[2]), None