##// END OF EJS Templates
audit-logs: handle query syntax in whosh query parser....
marcink -
r2520:80b18ba8 stable
parent child Browse files
Show More
@@ -1,123 +1,126 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from whoosh.qparser.default import QueryParser, query
23 from whoosh.qparser.default import QueryParser, query
24 from whoosh.qparser.dateparse import DateParserPlugin
24 from whoosh.qparser.dateparse import DateParserPlugin
25 from whoosh.fields import (TEXT, Schema, DATETIME)
25 from whoosh.fields import (TEXT, Schema, DATETIME)
26 from sqlalchemy.sql.expression import or_, and_, not_, func
26 from sqlalchemy.sql.expression import or_, and_, not_, func
27
27
28 from rhodecode.model.db import UserLog
28 from rhodecode.model.db import UserLog
29 from rhodecode.lib.utils2 import remove_prefix, remove_suffix, safe_unicode
29 from rhodecode.lib.utils2 import remove_prefix, remove_suffix, safe_unicode
30
30
31 # JOURNAL SCHEMA used only to generate queries in journal. We use whoosh
31 # JOURNAL SCHEMA used only to generate queries in journal. We use whoosh
32 # querylang to build sql queries and filter journals
32 # querylang to build sql queries and filter journals
33 JOURNAL_SCHEMA = Schema(
33 JOURNAL_SCHEMA = Schema(
34 username=TEXT(),
34 username=TEXT(),
35 date=DATETIME(),
35 date=DATETIME(),
36 action=TEXT(),
36 action=TEXT(),
37 repository=TEXT(),
37 repository=TEXT(),
38 ip=TEXT(),
38 ip=TEXT(),
39 )
39 )
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43
43
44 def user_log_filter(user_log, search_term):
44 def user_log_filter(user_log, search_term):
45 """
45 """
46 Filters sqlalchemy user_log based on search_term with whoosh Query language
46 Filters sqlalchemy user_log based on search_term with whoosh Query language
47 http://packages.python.org/Whoosh/querylang.html
47 http://packages.python.org/Whoosh/querylang.html
48
48
49 :param user_log:
49 :param user_log:
50 :param search_term:
50 :param search_term:
51 """
51 """
52 log.debug('Initial search term: %r' % search_term)
52 log.debug('Initial search term: %r' % search_term)
53 qry = None
53 qry = None
54 if search_term:
54 if search_term:
55 qp = QueryParser('repository', schema=JOURNAL_SCHEMA)
55 qp = QueryParser('repository', schema=JOURNAL_SCHEMA)
56 qp.add_plugin(DateParserPlugin())
56 qp.add_plugin(DateParserPlugin())
57 qry = qp.parse(safe_unicode(search_term))
57 qry = qp.parse(safe_unicode(search_term))
58 log.debug('Filtering using parsed query %r' % qry)
58 log.debug('Filtering using parsed query %r' % qry)
59
59
60 def wildcard_handler(col, wc_term):
60 def wildcard_handler(col, wc_term):
61 if wc_term.startswith('*') and not wc_term.endswith('*'):
61 if wc_term.startswith('*') and not wc_term.endswith('*'):
62 # postfix == endswith
62 # postfix == endswith
63 wc_term = remove_prefix(wc_term, prefix='*')
63 wc_term = remove_prefix(wc_term, prefix='*')
64 return func.lower(col).endswith(wc_term)
64 return func.lower(col).endswith(wc_term)
65 elif wc_term.startswith('*') and wc_term.endswith('*'):
65 elif wc_term.startswith('*') and wc_term.endswith('*'):
66 # wildcard == ilike
66 # wildcard == ilike
67 wc_term = remove_prefix(wc_term, prefix='*')
67 wc_term = remove_prefix(wc_term, prefix='*')
68 wc_term = remove_suffix(wc_term, suffix='*')
68 wc_term = remove_suffix(wc_term, suffix='*')
69 return func.lower(col).contains(wc_term)
69 return func.lower(col).contains(wc_term)
70
70
71 def get_filterion(field, val, term):
71 def get_filterion(field, val, term):
72
72
73 if field == 'repository':
73 if field == 'repository':
74 field = getattr(UserLog, 'repository_name')
74 field = getattr(UserLog, 'repository_name')
75 elif field == 'ip':
75 elif field == 'ip':
76 field = getattr(UserLog, 'user_ip')
76 field = getattr(UserLog, 'user_ip')
77 elif field == 'date':
77 elif field == 'date':
78 field = getattr(UserLog, 'action_date')
78 field = getattr(UserLog, 'action_date')
79 elif field == 'username':
79 elif field == 'username':
80 field = getattr(UserLog, 'username')
80 field = getattr(UserLog, 'username')
81 else:
81 else:
82 field = getattr(UserLog, field)
82 field = getattr(UserLog, field)
83 log.debug('filter field: %s val=>%s' % (field, val))
83 log.debug('filter field: %s val=>%s' % (field, val))
84
84
85 # sql filtering
85 # sql filtering
86 if isinstance(term, query.Wildcard):
86 if isinstance(term, query.Wildcard):
87 return wildcard_handler(field, val)
87 return wildcard_handler(field, val)
88 elif isinstance(term, query.Prefix):
88 elif isinstance(term, query.Prefix):
89 return func.lower(field).startswith(func.lower(val))
89 return func.lower(field).startswith(func.lower(val))
90 elif isinstance(term, query.DateRange):
90 elif isinstance(term, query.DateRange):
91 return and_(field >= val[0], field <= val[1])
91 return and_(field >= val[0], field <= val[1])
92 elif isinstance(term, query.Not):
92 elif isinstance(term, query.Not):
93 return not_(field == val)
93 return not_(field == val)
94 return func.lower(field) == func.lower(val)
94 return func.lower(field) == func.lower(val)
95
95
96 if isinstance(qry, (query.And, query.Not, query.Term, query.Prefix,
96 if isinstance(qry, (query.And, query.Not, query.Term, query.Prefix,
97 query.Wildcard, query.DateRange)):
97 query.Wildcard, query.DateRange)):
98 if not isinstance(qry, query.And):
98 if not isinstance(qry, query.And):
99 qry = [qry]
99 qry = [qry]
100
100
101 for term in qry:
101 for term in qry:
102 if isinstance(term, query.Not):
102 if isinstance(term, query.Not):
103 not_term = [z for z in term.leaves()][0]
103 not_term = [z for z in term.leaves()][0]
104 field = not_term.fieldname
104 field = not_term.fieldname
105 val = not_term.text
105 val = not_term.text
106 elif isinstance(term, query.DateRange):
106 elif isinstance(term, query.DateRange):
107 field = term.fieldname
107 field = term.fieldname
108 val = [term.startdate, term.enddate]
108 val = [term.startdate, term.enddate]
109 elif isinstance(term, query.NullQuery.__class__):
110 field = ''
111 val = ''
109 else:
112 else:
110 field = term.fieldname
113 field = term.fieldname
111 val = term.text
114 val = term.text
112
115 if field:
113 user_log = user_log.filter(get_filterion(field, val, term))
116 user_log = user_log.filter(get_filterion(field, val, term))
114 elif isinstance(qry, query.Or):
117 elif isinstance(qry, query.Or):
115 filters = []
118 filters = []
116 for term in qry:
119 for term in qry:
117 field = term.fieldname
120 field = term.fieldname
118 val = (term.text if not isinstance(term, query.DateRange)
121 val = (term.text if not isinstance(term, query.DateRange)
119 else [term.startdate, term.enddate])
122 else [term.startdate, term.enddate])
120 filters.append(get_filterion(field, val, term))
123 filters.append(get_filterion(field, val, term))
121 user_log = user_log.filter(or_(*filters))
124 user_log = user_log.filter(or_(*filters))
122
125
123 return user_log
126 return user_log
General Comments 0
You need to be logged in to leave comments. Login now