##// END OF EJS Templates
ignore: added gitignore
ignore: added gitignore

File last commit:

r112:998f0d14
r119:5de59700
Show More
validators.py
768 lines | 27.5 KiB | text/x-python | PythonLexer
# -*- coding: utf-8 -*-
# Copyright 2010 - 2017 RhodeCode GmbH and the AppEnlight project authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import datetime
import colander
from colander import null
# those keywords are here so we can distingush between searching for tags and
# normal properties of reports/logs
accepted_search_params = ['resource',
'request_id',
'start_date',
'end_date',
'page',
'min_occurences',
'http_status',
'priority',
'error',
'url_path',
'url_domain',
'report_status',
'min_duration',
'max_duration',
'message',
'level',
'namespace']
@colander.deferred
def deferred_utcnow(node, kw):
return kw['utcnow']
@colander.deferred
def optional_limited_date(node, kw):
if not kw.get('allow_permanent_storage'):
return limited_date
def lowercase_preparer(input_data):
"""
Transforms a list of string entries to lowercase
Used in search query validation
"""
if not input_data:
return input_data
return [x.lower() for x in input_data]
def shortener_factory(cutoff_size=32):
"""
Limits the input data to specific character count
:arg cutoff_cutoff_size How much characters to store
"""
def shortener(input_data):
if not input_data:
return input_data
else:
if isinstance(input_data, str):
return input_data[:cutoff_size]
else:
return input_data
return shortener
def cast_to_unicode_or_null(value):
if value is not colander.null:
return str(value)
return None
class NonTZDate(colander.DateTime):
""" Returns null for incorrect date format - also removes tz info"""
def deserialize(self, node, cstruct):
# disabled for now
# if cstruct and isinstance(cstruct, str):
# if ':' not in cstruct:
# cstruct += ':0.0'
# if '.' not in cstruct:
# cstruct += '.0'
value = super(NonTZDate, self).deserialize(node, cstruct)
if value:
return value.replace(tzinfo=None)
return value
class UnknownType(object):
"""
Universal type that will accept a deserialized JSON object and store it unaltered
"""
def serialize(self, node, appstruct):
if appstruct is null:
return null
return appstruct
def deserialize(self, node, cstruct):
if cstruct is null:
return null
return cstruct
def cstruct_children(self):
return []
# SLOW REPORT SCHEMA
def rewrite_type(input_data):
"""
Fix for legacy appenlight clients
"""
if input_data == 'remote_call':
return 'remote'
return input_data
class ExtraTupleSchema(colander.TupleSchema):
name = colander.SchemaNode(colander.String(),
validator=colander.Length(1, 64))
value = colander.SchemaNode(UnknownType(),
preparer=shortener_factory(512),
missing=None)
class ExtraSchemaList(colander.SequenceSchema):
tag = ExtraTupleSchema()
missing = None
class TagsTupleSchema(colander.TupleSchema):
name = colander.SchemaNode(colander.String(),
validator=colander.Length(1, 128))
value = colander.SchemaNode(UnknownType(),
preparer=shortener_factory(128),
missing=None)
class TagSchemaList(colander.SequenceSchema):
tag = TagsTupleSchema()
missing = None
class NumericTagsTupleSchema(colander.TupleSchema):
name = colander.SchemaNode(colander.String(),
validator=colander.Length(1, 128))
value = colander.SchemaNode(colander.Float(), missing=0)
class NumericTagSchemaList(colander.SequenceSchema):
tag = NumericTagsTupleSchema()
missing = None
class SlowCallSchema(colander.MappingSchema):
"""
Validates slow call format in slow call list
"""
start = colander.SchemaNode(NonTZDate())
end = colander.SchemaNode(NonTZDate())
statement = colander.SchemaNode(colander.String(), missing='')
parameters = colander.SchemaNode(UnknownType(), missing=None)
type = colander.SchemaNode(
colander.String(),
preparer=rewrite_type,
validator=colander.OneOf(
['tmpl', 'sql', 'nosql', 'remote', 'unknown', 'custom']),
missing='unknown')
subtype = colander.SchemaNode(colander.String(),
validator=colander.Length(1, 16),
missing='unknown')
location = colander.SchemaNode(colander.String(),
validator=colander.Length(1, 255),
missing='')
def limited_date(node, value):
""" checks to make sure that the value is not older/newer than 2h """
past_hours = 72
future_hours = 2
min_time = datetime.datetime.utcnow() - datetime.timedelta(
hours=past_hours)
max_time = datetime.datetime.utcnow() + datetime.timedelta(
hours=future_hours)
if min_time > value:
msg = '%r is older from current UTC time by ' + str(past_hours)
msg += ' hours. Ask administrator to enable permanent logging for ' \
'your application to store logs with dates in past.'
raise colander.Invalid(node, msg % value)
if max_time < value:
msg = '%r is newer from current UTC time by ' + str(future_hours)
msg += ' hours. Ask administrator to enable permanent logging for ' \
'your application to store logs with dates in future.'
raise colander.Invalid(node, msg % value)
class SlowCallListSchema(colander.SequenceSchema):
"""
Validates list of individual slow calls
"""
slow_call = SlowCallSchema()
class RequestStatsSchema(colander.MappingSchema):
"""
Validates format of requests statistics dictionary
"""
main = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
missing=0)
sql = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
missing=0)
nosql = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
missing=0)
remote = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
missing=0)
tmpl = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
missing=0)
custom = colander.SchemaNode(colander.Float(), validator=colander.Range(0),
missing=0)
sql_calls = colander.SchemaNode(colander.Float(),
validator=colander.Range(0),
missing=0)
nosql_calls = colander.SchemaNode(colander.Float(),
validator=colander.Range(0),
missing=0)
remote_calls = colander.SchemaNode(colander.Float(),
validator=colander.Range(0),
missing=0)
tmpl_calls = colander.SchemaNode(colander.Float(),
validator=colander.Range(0),
missing=0)
custom_calls = colander.SchemaNode(colander.Float(),
validator=colander.Range(0),
missing=0)
class FrameInfoVarSchema(colander.SequenceSchema):
"""
Validates format of frame variables of a traceback
"""
vars = colander.SchemaNode(UnknownType(),
validator=colander.Length(2, 2))
class FrameInfoSchema(colander.MappingSchema):
"""
Validates format of a traceback line
"""
cline = colander.SchemaNode(colander.String(), missing='')
module = colander.SchemaNode(colander.String(), missing='')
line = colander.SchemaNode(colander.String(), missing='')
file = colander.SchemaNode(colander.String(), missing='')
fn = colander.SchemaNode(colander.String(), missing='')
vars = FrameInfoVarSchema()
class FrameInfoListSchema(colander.SequenceSchema):
"""
Validates format of list of traceback lines
"""
frame = colander.SchemaNode(UnknownType())
class ReportDetailBaseSchema(colander.MappingSchema):
"""
Validates format of report - ie. request parameters and stats for a request in report group
"""
username = colander.SchemaNode(colander.String(),
preparer=[shortener_factory(255),
lambda x: x or ''],
missing='')
request_id = colander.SchemaNode(colander.String(),
preparer=shortener_factory(40),
missing='')
url = colander.SchemaNode(colander.String(),
preparer=shortener_factory(1024), missing='')
ip = colander.SchemaNode(colander.String(), preparer=shortener_factory(39),
missing=None)
start_time = colander.SchemaNode(NonTZDate(),
validator=optional_limited_date,
missing=deferred_utcnow)
end_time = colander.SchemaNode(NonTZDate(),
validator=optional_limited_date,
missing=None)
user_agent = colander.SchemaNode(colander.String(),
preparer=[shortener_factory(512),
lambda x: x or ''],
missing='')
message = colander.SchemaNode(colander.String(),
preparer=shortener_factory(2048),
missing='')
group_string = colander.SchemaNode(colander.String(),
preparer=shortener_factory(512),
missing=None)
request_stats = RequestStatsSchema(missing=None)
request = colander.SchemaNode(colander.Mapping(unknown='preserve'),
missing={})
traceback = FrameInfoListSchema(missing=None)
slow_calls = SlowCallListSchema(missing=[])
extra = ExtraSchemaList()
class ReportDetailSchema_0_5(ReportDetailBaseSchema):
pass
class ReportDetailSchemaPermissiveDate_0_5(ReportDetailSchema_0_5):
start_time = colander.SchemaNode(NonTZDate(), missing=deferred_utcnow)
end_time = colander.SchemaNode(NonTZDate(), missing=None)
class ReportSchemaBase(colander.MappingSchema):
"""
Validates format of report group
"""
client = colander.SchemaNode(colander.String(),
preparer=lambda x: x or 'unknown')
server = colander.SchemaNode(
colander.String(),
preparer=[
lambda x: x.lower() if x else 'unknown', shortener_factory(128)],
missing='unknown')
priority = colander.SchemaNode(colander.Int(),
preparer=[lambda x: x or 5],
validator=colander.Range(1, 10),
missing=5)
language = colander.SchemaNode(colander.String(), missing='unknown')
error = colander.SchemaNode(colander.String(),
preparer=shortener_factory(512),
missing='')
view_name = colander.SchemaNode(colander.String(),
preparer=[shortener_factory(128),
lambda x: x or ''],
missing='')
http_status = colander.SchemaNode(colander.Int(),
preparer=[lambda x: x or 200],
validator=colander.Range(1))
occurences = colander.SchemaNode(colander.Int(),
validator=colander.Range(1, 99999999999),
missing=1)
tags = TagSchemaList()
class ReportSchema_0_5(ReportSchemaBase, ReportDetailSchema_0_5):
pass
class ReportSchemaPermissiveDate_0_5(ReportSchemaBase,
ReportDetailSchemaPermissiveDate_0_5):
pass
class ReportListSchema_0_5(colander.SequenceSchema):
"""
Validates format of list of report groups
"""
report = ReportSchema_0_5()
validator = colander.Length(1)
class ReportListPermissiveDateSchema_0_5(colander.SequenceSchema):
"""
Validates format of list of report groups
"""
report = ReportSchemaPermissiveDate_0_5()
validator = colander.Length(1)
class LogSchema(colander.MappingSchema):
"""
Validates format if individual log entry
"""
primary_key = colander.SchemaNode(UnknownType(),
preparer=[cast_to_unicode_or_null,
shortener_factory(128)],
missing=None)
log_level = colander.SchemaNode(colander.String(),
preparer=shortener_factory(10),
missing='UNKNOWN')
message = colander.SchemaNode(colander.String(),
preparer=shortener_factory(4096),
missing='')
namespace = colander.SchemaNode(colander.String(),
preparer=shortener_factory(128),
missing='')
request_id = colander.SchemaNode(colander.String(),
preparer=shortener_factory(40),
missing='')
server = colander.SchemaNode(colander.String(),
preparer=shortener_factory(128),
missing='unknown')
date = colander.SchemaNode(NonTZDate(),
validator=limited_date,
missing=deferred_utcnow)
tags = TagSchemaList()
class LogSchemaPermanent(LogSchema):
date = colander.SchemaNode(NonTZDate(),
missing=deferred_utcnow)
permanent = colander.SchemaNode(colander.Boolean(), missing=False)
class LogListSchema(colander.SequenceSchema):
"""
Validates format of list of log entries
"""
log = LogSchema()
validator = colander.Length(1)
class LogListPermanentSchema(colander.SequenceSchema):
"""
Validates format of list of log entries
"""
log = LogSchemaPermanent()
validator = colander.Length(1)
class ViewRequestStatsSchema(RequestStatsSchema):
requests = colander.SchemaNode(colander.Integer(),
validator=colander.Range(0),
missing=0)
class ViewMetricTupleSchema(colander.TupleSchema):
"""
Validates list of views and their corresponding request stats object ie:
["dir/module:func",{"custom": 0.0..}]
"""
view_name = colander.SchemaNode(colander.String(),
preparer=[shortener_factory(128),
lambda x: x or 'unknown'],
missing='unknown')
metrics = ViewRequestStatsSchema()
class ViewMetricListSchema(colander.SequenceSchema):
"""
Validates view breakdown stats objects list
{metrics key of server/time object}
"""
view_tuple = ViewMetricTupleSchema()
validator = colander.Length(1)
class ViewMetricSchema(colander.MappingSchema):
"""
Validates server/timeinterval object, ie:
{server/time object}
"""
timestamp = colander.SchemaNode(NonTZDate(),
validator=limited_date,
missing=None)
server = colander.SchemaNode(colander.String(),
preparer=[shortener_factory(128),
lambda x: x or 'unknown'],
missing='unknown')
metrics = ViewMetricListSchema()
class GeneralMetricSchema(colander.MappingSchema):
"""
Validates universal metric schema
"""
namespace = colander.SchemaNode(colander.String(), missing='',
preparer=shortener_factory(128))
server_name = colander.SchemaNode(colander.String(),
preparer=[shortener_factory(128),
lambda x: x or 'unknown'],
missing='unknown')
timestamp = colander.SchemaNode(NonTZDate(), validator=limited_date,
missing=deferred_utcnow)
tags = TagSchemaList(missing=colander.required)
class GeneralMetricPermanentSchema(GeneralMetricSchema):
"""
Validates universal metric schema
"""
timestamp = colander.SchemaNode(NonTZDate(), missing=deferred_utcnow)
class GeneralMetricsListSchema(colander.SequenceSchema):
metric = GeneralMetricSchema()
validator = colander.Length(1)
class GeneralMetricsPermanentListSchema(colander.SequenceSchema):
metric = GeneralMetricPermanentSchema()
validator = colander.Length(1)
class MetricsListSchema(colander.SequenceSchema):
"""
Validates list of metrics objects ie:
[{server/time object}, ] part
"""
metric = ViewMetricSchema()
validator = colander.Length(1)
class StringToAppList(object):
"""
Returns validated list of application ids from user query and
set of applications user is allowed to look at
transform string to list containing single integer
"""
def serialize(self, node, appstruct):
if appstruct is null:
return null
return appstruct
def deserialize(self, node, cstruct):
if cstruct is null:
return null
apps = set([int(a) for a in node.bindings['resources']])
if isinstance(cstruct, str):
cstruct = [cstruct]
cstruct = [int(a) for a in cstruct]
valid_apps = list(apps.intersection(set(cstruct)))
if valid_apps:
return valid_apps
return null
def cstruct_children(self):
return []
@colander.deferred
def possible_applications_validator(node, kw):
possible_apps = [int(a) for a in kw['resources']]
return colander.All(colander.ContainsOnly(possible_apps),
colander.Length(1))
@colander.deferred
def possible_applications(node, kw):
return [int(a) for a in kw['resources']]
@colander.deferred
def today_start(node, kw):
return datetime.datetime.utcnow().replace(second=0, microsecond=0,
minute=0,
hour=0)
@colander.deferred
def today_end(node, kw):
return datetime.datetime.utcnow().replace(second=0, microsecond=0,
minute=59, hour=23)
@colander.deferred
def old_start(node, kw):
t_delta = datetime.timedelta(days=90)
return datetime.datetime.utcnow().replace(second=0, microsecond=0,
minute=0,
hour=0) - t_delta
@colander.deferred
def today_end(node, kw):
return datetime.datetime.utcnow().replace(second=0, microsecond=0,
minute=59, hour=23)
class PermissiveDate(colander.DateTime):
""" Returns null for incorrect date format - also removes tz info"""
def deserialize(self, node, cstruct):
if not cstruct:
return null
try:
result = colander.iso8601.parse_date(
cstruct, default_timezone=self.default_tzinfo)
except colander.iso8601.ParseError:
return null
return result.replace(tzinfo=None)
class LogSearchSchema(colander.MappingSchema):
def schema_type(self, **kw):
return colander.Mapping(unknown='preserve')
resource = colander.SchemaNode(StringToAppList(),
validator=possible_applications_validator,
missing=possible_applications)
message = colander.SchemaNode(colander.Sequence(accept_scalar=True),
colander.SchemaNode(colander.String()),
missing=None)
level = colander.SchemaNode(colander.Sequence(accept_scalar=True),
colander.SchemaNode(colander.String()),
preparer=lowercase_preparer,
missing=None)
namespace = colander.SchemaNode(colander.Sequence(accept_scalar=True),
colander.SchemaNode(colander.String()),
preparer=lowercase_preparer,
missing=None)
request_id = colander.SchemaNode(colander.Sequence(accept_scalar=True),
colander.SchemaNode(colander.String()),
preparer=lowercase_preparer,
missing=None)
start_date = colander.SchemaNode(PermissiveDate(),
missing=None)
end_date = colander.SchemaNode(PermissiveDate(),
missing=None)
page = colander.SchemaNode(colander.Integer(),
validator=colander.Range(min=1),
missing=1)
class ReportSearchSchema(colander.MappingSchema):
def schema_type(self, **kw):
return colander.Mapping(unknown='preserve')
resource = colander.SchemaNode(StringToAppList(),
validator=possible_applications_validator,
missing=possible_applications)
request_id = colander.SchemaNode(colander.Sequence(accept_scalar=True),
colander.SchemaNode(colander.String()),
missing=None)
start_date = colander.SchemaNode(PermissiveDate(),
missing=None)
end_date = colander.SchemaNode(PermissiveDate(),
missing=None)
page = colander.SchemaNode(colander.Integer(),
validator=colander.Range(min=1),
missing=1)
min_occurences = colander.SchemaNode(
colander.Sequence(accept_scalar=True),
colander.SchemaNode(colander.Integer()),
missing=None)
http_status = colander.SchemaNode(colander.Sequence(accept_scalar=True),
colander.SchemaNode(colander.Integer()),
missing=None)
priority = colander.SchemaNode(colander.Sequence(accept_scalar=True),
colander.SchemaNode(colander.Integer()),
missing=None)
error = colander.SchemaNode(colander.Sequence(accept_scalar=True),
colander.SchemaNode(colander.String()),
missing=None)
url_path = colander.SchemaNode(colander.Sequence(accept_scalar=True),
colander.SchemaNode(colander.String()),
missing=None)
url_domain = colander.SchemaNode(colander.Sequence(accept_scalar=True),
colander.SchemaNode(colander.String()),
missing=None)
report_status = colander.SchemaNode(colander.Sequence(accept_scalar=True),
colander.SchemaNode(colander.String()),
missing=None)
min_duration = colander.SchemaNode(colander.Sequence(accept_scalar=True),
colander.SchemaNode(colander.Float()),
missing=None)
max_duration = colander.SchemaNode(colander.Sequence(accept_scalar=True),
colander.SchemaNode(colander.Float()),
missing=None)
class TagSchema(colander.MappingSchema):
"""
Used in log search
"""
name = colander.SchemaNode(colander.String(),
validator=colander.Length(1, 32))
value = colander.SchemaNode(colander.Sequence(accept_scalar=True),
colander.SchemaNode(colander.String(),
validator=colander.Length(
1, 128)),
missing=None)
op = colander.SchemaNode(colander.String(),
validator=colander.Length(1, 128),
missing=None)
class TagListSchema(colander.SequenceSchema):
tag = TagSchema()
class RuleFieldType(object):
""" Validator which succeeds if the value passed to it is one of
a fixed set of values """
def __init__(self, cast_to):
self.cast_to = cast_to
def __call__(self, node, value):
try:
if self.cast_to == 'int':
int(value)
elif self.cast_to == 'float':
float(value)
elif self.cast_to == 'unicode':
str(value)
except:
raise colander.Invalid(node,
"Can't cast {} to {}".format(
value, self.cast_to))
def build_rule_schema(ruleset, check_matrix):
"""
Accepts ruleset and a map of fields/possible operations and builds
validation class
"""
schema = colander.SchemaNode(colander.Mapping())
schema.add(colander.SchemaNode(colander.String(), name='field'))
if ruleset['field'] in ['__AND__', '__OR__', '__NOT__']:
subrules = colander.SchemaNode(colander.Tuple(), name='rules')
for rule in ruleset['rules']:
subrules.add(build_rule_schema(rule, check_matrix))
schema.add(subrules)
else:
op_choices = check_matrix[ruleset['field']]['ops']
cast_to = check_matrix[ruleset['field']]['type']
schema.add(colander.SchemaNode(colander.String(),
validator=colander.OneOf(op_choices),
name='op'))
schema.add(colander.SchemaNode(colander.String(),
name='value',
validator=RuleFieldType(cast_to)))
return schema
class ConfigTypeSchema(colander.MappingSchema):
type = colander.SchemaNode(colander.String(), missing=None)
config = colander.SchemaNode(UnknownType(), missing=None)
class MappingListSchema(colander.SequenceSchema):
config = colander.SchemaNode(UnknownType())