New upstream version 3.11.1
Sophie Brun
3 years ago
306 | 306 | stage: build |
307 | 307 | image: centos:7 |
308 | 308 | before_script: |
309 | - yum -y upgrade | |
310 | - yum -y install which git | |
311 | - yum -y install epel-release | |
312 | - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/faradaysec/faraday-linux-installers-builder.git | |
313 | - mv py3.tar / | |
314 | - cd /; tar xf py3.tar; cd - | |
315 | - yum -y install ruby ruby-devel rubygems gobject-introspection-devel curl zsh mailcap libffi-devel openssl-devel openldap-devel libxslt-devel libxml2-devel libxslt-devel freetype-devel libjpeg-devel gtk+-devel gtk3-devel gtk2-devel postgresql-devel | |
316 | - yum groups -y install "Development Tools" | |
317 | - yum -y install centos-release-scl | |
318 | - yum -y install rh-python36 | |
319 | - source /opt/rh/rh-python36/enable | |
320 | - pip install virtualenv | |
321 | - pip install virtualenv-tools3 | |
309 | - yum -y upgrade | |
310 | - yum -y install which git epel-release centos-release-scl | |
311 | - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/faradaysec/faraday-linux-installers-builder.git | |
312 | - mv py3.tar / | |
313 | - cd /; tar xf py3.tar; cd - | |
314 | - yum -y install curl zsh mailcap libffi-devel openssl-devel openldap-devel libjpeg-devel postgresql-devel | |
315 | - yum groups -y install "Development Tools" | |
316 | - yum -y install rh-python36 rh-ruby23 rh-ruby23-ruby-devel | |
317 | - source /opt/rh/rh-ruby23/enable | |
318 | - export X_SCLS="`scl enable rh-ruby23 'echo $X_SCLS'`" | |
319 | - source /opt/rh/rh-python36/enable | |
320 | - pip install virtualenv | |
321 | - pip install virtualenv-tools3 | |
322 | 322 | script: |
323 | 323 | - mkdir build_installer |
324 | 324 | - cp -a faraday.tar.gz build_installer/. |
0 | Jun 3rd, 2020 |
0 | * Fix missing shodan icon and invalid link in dashboard and hosts list | |
1 | * Upgrade marshmallow, webargs, werkzeug and flask-login dependencies to | |
2 | latest versions in order to make packaging for distros easier |
7 | 7 | New features in the latest update |
8 | 8 | ===================================== |
9 | 9 | |
10 | ||
11 | 3.11.1 [Jun 3rd, 2020]: | |
12 | --- | |
13 | * Fix missing shodan icon and invalid link in dashboard and hosts list | |
14 | * Upgrade marshmallow, webargs, werkzeug and flask-login dependencies to | |
15 | latest versions in order to make packaging for distros easier | |
10 | 16 | |
11 | 17 | 3.11 [Apr 22nd, 2020]: |
12 | 18 | --- |
7 | 7 | New features in the latest update |
8 | 8 | ===================================== |
9 | 9 | |
10 | ||
11 | 3.11.1 [Jun 3rd, 2020]: | |
12 | --- | |
13 | * Fix missing shodan icon and invalid link in dashboard and hosts list | |
14 | * Upgrade marshmallow, webargs, werkzeug and flask-login dependencies to | |
15 | latest versions in order to make packaging for distros easier | |
10 | 16 | |
11 | 17 | 3.11 [Apr 22nd, 2020]: |
12 | 18 | --- |
1 | 1 | # Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/) |
2 | 2 | # See the file 'doc/LICENSE' for the license information |
3 | 3 | |
4 | __version__ = '3.11' | |
4 | __version__ = '3.11.1' | |
5 | 5 | __license_version__ = __version__ |
8 | 8 | |
9 | 9 | import flask |
10 | 10 | import sqlalchemy |
11 | import datetime | |
11 | 12 | from collections import defaultdict |
12 | 13 | from flask import g |
13 | 14 | from flask_classful import FlaskView |
15 | 16 | from sqlalchemy.orm.exc import NoResultFound, ObjectDeletedError |
16 | 17 | from sqlalchemy.inspection import inspect |
17 | 18 | from sqlalchemy import func, desc, asc |
18 | from marshmallow import Schema | |
19 | from marshmallow.compat import with_metaclass | |
19 | from marshmallow import Schema, EXCLUDE, fields | |
20 | 20 | from marshmallow.validate import Length |
21 | 21 | from marshmallow_sqlalchemy import ModelConverter |
22 | 22 | from marshmallow_sqlalchemy.schema import ModelSchemaMeta, ModelSchemaOpts |
23 | from webargs.flaskparser import FlaskParser, parser as parser_imported | |
23 | from webargs.flaskparser import FlaskParser | |
24 | 24 | from webargs.core import ValidationError |
25 | 25 | from faraday.server.models import Workspace, db, Command, CommandObject |
26 | 26 | from faraday.server.schemas import NullToBlankString |
161 | 161 | def _get_schema_instance(self, route_kwargs, **kwargs): |
162 | 162 | """Instances a model schema. |
163 | 163 | |
164 | By default it uses sets strict to True | |
165 | but this can be overriden as well as any other parameters in | |
166 | the function's kwargs. | |
167 | ||
168 | 164 | It also uses _set_schema_context to set the context of the |
169 | 165 | schema. |
170 | 166 | """ |
171 | if 'strict' not in kwargs: | |
172 | kwargs['strict'] = True | |
173 | 167 | kwargs['context'] = self._set_schema_context( |
174 | 168 | kwargs.get('context', {}), **route_kwargs) |
169 | ||
170 | # If the client send us fields that are not in the schema, ignore them | |
171 | # This is the default in marshmallow 2, but not in marshmallow 3 | |
172 | kwargs['unknown'] = EXCLUDE | |
173 | ||
175 | 174 | return self._get_schema_class()(**kwargs) |
176 | 175 | |
177 | 176 | def _set_schema_context(self, context, **kwargs): |
284 | 283 | TODO migration: document route_kwargs |
285 | 284 | """ |
286 | 285 | try: |
287 | return self._get_schema_instance(route_kwargs, **kwargs).dump(obj).data | |
286 | return self._get_schema_instance(route_kwargs, **kwargs).dump(obj) | |
288 | 287 | except ObjectDeletedError: |
289 | 288 | return [] |
290 | 289 | |
293 | 292 | data. It a ``Marshmallow.Schema`` instance to perform the |
294 | 293 | deserialization |
295 | 294 | """ |
296 | return FlaskParser().parse(schema, request, locations=('json',), | |
295 | return FlaskParser().parse(schema, request, location="json", | |
297 | 296 | *args, **kwargs) |
298 | 297 | |
299 | 298 | @classmethod |
713 | 712 | { |
714 | 713 | 'message': 'Existing value', |
715 | 714 | 'object': self._get_schema_class()().dump( |
716 | conflict_obj).data, | |
715 | conflict_obj), | |
717 | 716 | } |
718 | 717 | )) |
719 | 718 | else: |
822 | 821 | { |
823 | 822 | 'message': 'Existing value', |
824 | 823 | 'object': self._get_schema_class()().dump( |
825 | conflict_obj).data, | |
824 | conflict_obj), | |
826 | 825 | } |
827 | 826 | )) |
828 | 827 | else: |
906 | 905 | { |
907 | 906 | 'message': 'Existing value', |
908 | 907 | 'object': self._get_schema_class()().dump( |
909 | conflict_obj).data, | |
908 | conflict_obj), | |
910 | 909 | } |
911 | 910 | )) |
912 | 911 | else: |
1213 | 1212 | self.model_converter = CustomModelConverter |
1214 | 1213 | |
1215 | 1214 | |
1216 | class AutoSchema(with_metaclass(ModelSchemaMeta, Schema)): | |
1215 | class DictWithData(dict): | |
1216 | """Like a dict, but with a data attribute pointing to itself. | |
1217 | ||
1218 | This was designed to make code marshmallow 2/3 compatible: | |
1219 | ||
1220 | Schema().load and Schema().dump don’t return a (data, errors) | |
1221 | [named]tuple any more. Only data is returned. | |
1222 | ||
1223 | This class will avoid a lot of AttributeErrors with old marshmallow code | |
1224 | using `Schema().dump().data`. | |
1225 | ||
1226 | """ | |
1227 | # TODO remove this class | |
1228 | @property | |
1229 | def data(self): | |
1230 | return self | |
1231 | ||
1232 | ||
1233 | # Restore marshmallow's DateTime field behavior of marshmallow 2 so it adds | |
1234 | # "+00:00" to the serialized date. This ugly hack was done to keep our API | |
1235 | # backwards-compatible. Yes, it's horrible. | |
1236 | # Also, I'm putting it here because this file will be always imported in a very | |
1237 | # early stage, before defining any schemas. | |
1238 | # This commit broke backwards compatibility: https://github.com/marshmallow-code/marshmallow/commit/610ec20ea3be89684f7e4df8035d163c3561c904 | |
1239 | # TODO check if we can remove this | |
1240 | def old_isoformat(dt, *args, **kwargs): | |
1241 | """Return the ISO8601-formatted UTC representation of a datetime object.""" | |
1242 | if dt.tzinfo is None: | |
1243 | dt = dt.replace(tzinfo=datetime.timezone.utc) | |
1244 | else: | |
1245 | dt = dt.astimezone(datetime.timezone.utc) | |
1246 | return dt.isoformat(*args, **kwargs) | |
1247 | fields.DateTime.SERIALIZATION_FUNCS['iso'] = old_isoformat | |
1248 | ||
1249 | ||
1250 | class AutoSchema(Schema, metaclass=ModelSchemaMeta): | |
1217 | 1251 | """ |
1218 | 1252 | A Marshmallow schema that does field introspection based on |
1219 | 1253 | the SQLAlchemy model specified in Meta.model. |
1226 | 1260 | TYPE_MAPPING = Schema.TYPE_MAPPING.copy() |
1227 | 1261 | TYPE_MAPPING[str] = NullToBlankString |
1228 | 1262 | |
1263 | def __init__(self, strict=None, *args, **kwargs): | |
1264 | # assert kwargs.get('unknown') == EXCLUDE, ( | |
1265 | # "This schema was initiatized without unknown=EXCLUDE. This means " | |
1266 | # "that it will fail if you pass it fields that do not exist in the " | |
1267 | # "schema, instead of ignoring them. I suppose this isn't what you " | |
1268 | # "want. If you do want this behavior, consider removing this assert." | |
1269 | # ) | |
1270 | ||
1271 | # TODO: remove strict argument. This was introduced during the | |
1272 | # migration to marshmallow 3. Once all occurences of strict=True are | |
1273 | # removed, this is not required anymore | |
1274 | ||
1275 | super(AutoSchema, self).__init__(*args, **kwargs) | |
1276 | self.unknown = EXCLUDE | |
1277 | ||
1278 | def load(self, *args, **kwargs): | |
1279 | # TODO remove this marshmallow 2/3 compatibility hack | |
1280 | ret = super(AutoSchema, self).load(*args, **kwargs) | |
1281 | if isinstance(ret, list) and all(isinstance(e, dict) for e in ret): | |
1282 | ret = [DictWithData(e) for e in ret] | |
1283 | elif isinstance(ret, dict): | |
1284 | ret = DictWithData(ret) | |
1285 | return ret | |
1286 | ||
1287 | def dump(self, *args, **kwargs): | |
1288 | # TODO remove this marshmallow 2/3 compatibility hack | |
1289 | ret = super(AutoSchema, self).dump(*args, **kwargs) | |
1290 | if isinstance(ret, list) and all(isinstance(e, dict) for e in ret): | |
1291 | ret = [DictWithData(e) for e in ret] | |
1292 | elif isinstance(ret, dict): | |
1293 | ret = DictWithData(ret) | |
1294 | return ret | |
1295 | ||
1296 | ||
1229 | 1297 | |
1230 | 1298 | class FilterAlchemyModelConverter(ModelConverter): |
1231 | 1299 | """Use this to make all fields of a model not required. |
1238 | 1306 | kwargs['required'] = False |
1239 | 1307 | |
1240 | 1308 | |
1309 | class AutoSchemaFlaskParser(FlaskParser): | |
1310 | # It is required to use a schema class that has unknown=EXCLUDE by default. | |
1311 | # Otherwise, requests would fail if a not defined query parameter is sent | |
1312 | # (like group_by) | |
1313 | DEFAULT_SCHEMA_CLASS = AutoSchema | |
1314 | ||
1315 | ||
1241 | 1316 | class FilterSetMeta: |
1242 | 1317 | """Base Meta class of FilterSet objects""" |
1243 | parser = parser_imported | |
1318 | parser = AutoSchemaFlaskParser(location='query') | |
1244 | 1319 | converter = FilterAlchemyModelConverter() |
1245 | 1320 | |
1246 | 1321 |
5 | 5 | |
6 | 6 | from flask import Blueprint, abort, request |
7 | 7 | from flask_classful import route |
8 | from marshmallow import fields, Schema | |
8 | from marshmallow import fields, Schema, EXCLUDE | |
9 | 9 | from sqlalchemy.orm.exc import NoResultFound |
10 | 10 | |
11 | 11 | |
108 | 108 | |
109 | 109 | |
110 | 110 | class AgentRunSchema(Schema): |
111 | executorData = fields.Nested(ExecutorDataSchema(), required=True) | |
111 | executorData = fields.Nested( | |
112 | ExecutorDataSchema(unknown=EXCLUDE), | |
113 | required=True | |
114 | ) | |
112 | 115 | |
113 | 116 | |
114 | 117 | class AgentView(UpdateWorkspacedMixin, |
137 | 140 | """ |
138 | 141 | if flask.request.content_type != 'application/json': |
139 | 142 | abort(400, "Only application/json is a valid content-type") |
140 | data = self._parse_data(AgentRunSchema(strict=True), request) | |
143 | data = self._parse_data(AgentRunSchema(unknown=EXCLUDE), request) | |
141 | 144 | agent = self._get_object(agent_id, workspace_name) |
142 | 145 | executor_data = data['executorData'] |
143 | 146 |
24 | 24 | |
25 | 25 | def index(self): |
26 | 26 | return AgentAuthTokenSchema().dump( |
27 | {'token': faraday_server.agent_token}).data | |
27 | {'token': faraday_server.agent_token}) | |
28 | 28 | |
29 | 29 | def post(self): |
30 | 30 | from faraday.server.app import save_new_agent_creation_token # pylint:disable=import-outside-toplevel |
34 | 34 | flask.abort(403) |
35 | 35 | save_new_agent_creation_token() |
36 | 36 | return AgentAuthTokenSchema().dump( |
37 | {'token': faraday_server.agent_token}).data | |
37 | {'token': faraday_server.agent_token}) | |
38 | 38 | |
39 | 39 | |
40 | 40 | AgentAuthTokenView.register(agent_auth_token_api) |
62 | 62 | def __init__(self, *args, **kwargs): |
63 | 63 | super(PolymorphicVulnerabilityField, self).__init__(*args, **kwargs) |
64 | 64 | self.many = kwargs.get('many', False) |
65 | self.vuln_schema = VulnerabilitySchema(strict=True) | |
66 | self.vulnweb_schema = BulkVulnerabilityWebSchema(strict=True) | |
67 | ||
68 | def _deserialize(self, value, attr, data): | |
65 | self.vuln_schema = VulnerabilitySchema() | |
66 | self.vulnweb_schema = BulkVulnerabilityWebSchema() | |
67 | ||
68 | def _deserialize(self, value, attr, data, **kwargs): | |
69 | 69 | if self.many and not utils.is_collection(value): |
70 | 70 | self.fail('type', input=value, type=value.__class__.__name__) |
71 | 71 | if self.many: |
83 | 83 | schema = self.vulnweb_schema |
84 | 84 | else: |
85 | 85 | raise ValidationError('type must be "Vulnerability" or "VulnerabilityWeb"') |
86 | return schema.load(value).data | |
86 | return schema.load(value) | |
87 | 87 | |
88 | 88 | |
89 | 89 | class BulkCredentialSchema(AutoSchema): |
96 | 96 | """It's like the original service schema, but now it only uses port |
97 | 97 | instead of ports (a single integer array). That field was only used |
98 | 98 | to keep backwards compatibility with the Web UI""" |
99 | port = fields.Integer(strict=True, required=True, | |
99 | port = fields.Integer(required=True, | |
100 | 100 | validate=[Range(min=0, error="The value must be greater than or equal to 0")]) |
101 | 101 | vulnerabilities = PolymorphicVulnerabilityField( |
102 | VulnerabilitySchema(many=True), | |
102 | # VulnerabilitySchema(many=True), # I have no idea what this line does, but breaks with marshmallow 3 | |
103 | 103 | many=True, |
104 | 104 | missing=[], |
105 | 105 | ) |
158 | 158 | ) |
159 | 159 | |
160 | 160 | @post_load |
161 | def load_end_date(self, data): | |
161 | def load_end_date(self, data, **kwargs): | |
162 | 162 | duration = data.pop('duration') |
163 | 163 | data['end_date'] = data['start_date'] + duration |
164 | return data | |
164 | 165 | |
165 | 166 | |
166 | 167 | class BulkCreateSchema(Schema): |
203 | 204 | |
204 | 205 | def bulk_create(ws, data, data_already_deserialized=False): |
205 | 206 | if not data_already_deserialized: |
206 | schema = BulkCreateSchema(strict=True) | |
207 | data = schema.load(data).data | |
207 | schema = BulkCreateSchema() | |
208 | data = schema.load(data) | |
208 | 209 | if 'command' in data: |
209 | 210 | command = _create_command(ws, data['command']) |
210 | 211 | else: |
8 | 8 | |
9 | 9 | from faraday.server.api.base import AutoSchema, ReadWriteWorkspacedView, PaginatedMixin |
10 | 10 | from faraday.server.models import Command, Workspace |
11 | from faraday.server.schemas import PrimaryKeyRelatedField | |
11 | from faraday.server.schemas import MutableField, PrimaryKeyRelatedField | |
12 | 12 | |
13 | 13 | commandsrun_api = Blueprint('commandsrun_api', __name__) |
14 | 14 | |
16 | 16 | class CommandSchema(AutoSchema): |
17 | 17 | _id = fields.Integer(dump_only=True, attribute='id') |
18 | 18 | itime = fields.Method(serialize='get_itime', deserialize='load_itime', required=True, attribute='start_date') |
19 | duration = fields.Method(serialize='get_duration', allow_none=True) | |
19 | duration = MutableField( | |
20 | fields.Method(serialize='get_duration'), | |
21 | fields.Integer(), | |
22 | allow_none=True) | |
20 | 23 | workspace = PrimaryKeyRelatedField('name', dump_only=True) |
21 | 24 | creator = PrimaryKeyRelatedField('username', dump_only=True) |
22 | 25 | |
39 | 42 | return 'In progress' |
40 | 43 | |
41 | 44 | @post_load |
42 | def post_load_set_end_date_with_duration(self, data): | |
45 | def post_load_set_end_date_with_duration(self, data, **kwargs): | |
43 | 46 | # there is a potential bug when updating, the start_date can be changed. |
44 | 47 | duration = data.pop('duration', None) |
45 | 48 | if duration: |
46 | data['end_date'] = data['start_date'] + datetime.timedelta(seconds=duration) | |
49 | data['end_date'] = data['start_date'] + datetime.timedelta( | |
50 | seconds=duration) | |
51 | return data | |
47 | 52 | |
48 | 53 | class Meta: |
49 | 54 | model = Command |
71 | 71 | abort(409, ValidationError( |
72 | 72 | { |
73 | 73 | 'message': 'Comment already exists', |
74 | 'object': self.schema_class().dump(comment).data, | |
74 | 'object': self.schema_class().dump(comment), | |
75 | 75 | } |
76 | 76 | )) |
77 | 77 | res = super(UniqueCommentView, self)._perform_create(data, workspace_name) |
66 | 66 | ) |
67 | 67 | |
68 | 68 | @post_load |
69 | def set_parent(self, data): | |
69 | def set_parent(self, data, **kwargs): | |
70 | 70 | parent_type = data.pop('parent_type', None) |
71 | 71 | parent_id = data.pop('parent', None) |
72 | 72 | if parent_type == 'Host': |
208 | 208 | @route('/<host_id>/services/') |
209 | 209 | def service_list(self, workspace_name, host_id): |
210 | 210 | services = self._get_object(host_id, workspace_name).services |
211 | return ServiceSchema(many=True).dump(services).data | |
211 | return ServiceSchema(many=True).dump(services) | |
212 | 212 | |
213 | 213 | @route('/countVulns/') |
214 | 214 | def count_vulns(self, workspace_name): |
236 | 236 | host_count = Host.query_with_count(None, host_id_list, workspace_name) |
237 | 237 | |
238 | 238 | for host in host_count.all(): |
239 | res_dict["hosts"][host.id] = host_count_schema.dump(host).data | |
239 | res_dict["hosts"][host.id] = host_count_schema.dump(host) | |
240 | 240 | # return counts.data |
241 | 241 | |
242 | 242 | return res_dict |
34 | 34 | required=True, |
35 | 35 | attribute='port') |
36 | 36 | status = fields.String(missing='open', validate=OneOf(Service.STATUSES), |
37 | required=True, allow_none=False) | |
37 | allow_none=False) | |
38 | 38 | parent = fields.Integer(attribute='host_id') # parent is not required for updates |
39 | 39 | host_id = fields.Integer(attribute='host_id', dump_only=True) |
40 | 40 | vulns = fields.Integer(attribute='vulnerability_count', dump_only=True) |
61 | 61 | return str(port) |
62 | 62 | |
63 | 63 | @post_load |
64 | def post_load_parent(self, data): | |
64 | def post_load_parent(self, data, **kwargs): | |
65 | 65 | """Gets the host_id from parent attribute. Pops it and tries to |
66 | 66 | get a Host with that id in the corresponding workspace. |
67 | 67 | """ |
69 | 69 | if self.context['updating']: |
70 | 70 | if host_id is None: |
71 | 71 | # Partial update? |
72 | return | |
72 | return data | |
73 | 73 | |
74 | 74 | if host_id != self.context['object'].parent.id: |
75 | 75 | raise ValidationError('Can\'t change service parent.') |
85 | 85 | ).one() |
86 | 86 | except NoResultFound: |
87 | 87 | raise ValidationError('Host with id {} not found'.format(host_id)) |
88 | ||
89 | return data | |
88 | 90 | |
89 | 91 | class Meta: |
90 | 92 | model = Service |
11 | 11 | |
12 | 12 | @session_api.route('/session') |
13 | 13 | def session_info(): |
14 | user = current_app.user_datastore.get_user(session['user_id']) | |
14 | user = current_app.user_datastore.get_user(session['_user_id']) # TODO use public flask_login functions | |
15 | 15 | data = user.get_security_payload() |
16 | 16 | data['csrf_token'] = generate_csrf() |
17 | 17 | data['preferences'] = user.preferences |
106 | 106 | return references |
107 | 107 | |
108 | 108 | @post_load |
109 | def post_load_impact(self, data): | |
109 | def post_load_impact(self, data, **kwargs): | |
110 | 110 | # Unflatten impact (move data[impact][*] to data[*]) |
111 | 111 | impact = data.pop('impact', None) |
112 | 112 | if impact: |
20 | 20 | from sqlalchemy.orm import aliased, joinedload, selectin_polymorphic, undefer |
21 | 21 | from sqlalchemy.orm.exc import NoResultFound |
22 | 22 | from sqlalchemy import desc, or_ |
23 | from werkzeug.datastructures import ImmutableMultiDict | |
23 | 24 | |
24 | 25 | from depot.manager import DepotManager |
25 | 26 | from faraday.server.api.base import ( |
170 | 171 | |
171 | 172 | for file_obj in obj.evidence: |
172 | 173 | try: |
173 | ret, errors = EvidenceSchema().dump(file_obj) | |
174 | if errors: | |
175 | raise ValidationError(errors, data=ret) | |
176 | res[file_obj.filename] = ret | |
174 | res[file_obj.filename] = EvidenceSchema().dump(file_obj) | |
177 | 175 | except IOError: |
178 | 176 | logger.warning("File not found. Did you move your server?") |
179 | 177 | |
220 | 218 | return value |
221 | 219 | |
222 | 220 | @post_load |
223 | def post_load_impact(self, data): | |
221 | def post_load_impact(self, data, **kwargs): | |
224 | 222 | # Unflatten impact (move data[impact][*] to data[*]) |
225 | 223 | impact = data.pop('impact', None) |
226 | 224 | if impact: |
228 | 226 | return data |
229 | 227 | |
230 | 228 | @post_load |
231 | def post_load_parent(self, data): | |
229 | def post_load_parent(self, data, **kwargs): | |
232 | 230 | # schema guarantees that parent_type exists. |
233 | 231 | parent_class = None |
234 | 232 | parent_type = data.pop('parent_type', None) |
235 | 233 | parent_id = data.pop('parent', None) |
236 | 234 | if not (parent_type and parent_id): |
237 | 235 | # Probably a partial load, since they are required |
238 | return | |
236 | return data | |
239 | 237 | if parent_type == 'Host': |
240 | 238 | parent_class = Host |
241 | 239 | parent_field = 'host_id' |
369 | 367 | # TODO migration: Check if we should add fields owner, |
370 | 368 | # command, impact, issuetracker, tags, date, host |
371 | 369 | # evidence, policy violations, hostnames |
370 | ||
372 | 371 | fields = ( |
373 | "id", "status", "website", "pname", "query", "path", "service", | |
372 | "id", "status", "website", "parameter_name", "query_string", "path", "service", | |
374 | 373 | "data", "severity", "confirmed", "name", "request", "response", |
375 | "parameters", "params", "resolution", "ease_of_resolution", | |
374 | "parameters", "resolution", | |
376 | 375 | "description", "command_id", "target", "creator", "method", |
377 | "easeofresolution", "query_string", "parameter_name", "service_id", | |
376 | "ease_of_resolution", "service_id", | |
378 | 377 | "status_code", "tool", |
379 | 378 | ) |
380 | 379 | |
381 | 380 | strict_fields = ( |
382 | "severity", "confirmed", "method", "status", "easeofresolution", | |
383 | "ease_of_resolution", "service_id", | |
381 | "severity", "confirmed", "method", "status", "ease_of_resolution", | |
382 | "service_id", | |
384 | 383 | ) |
385 | 384 | |
386 | 385 | default_operator = CustomILike |
396 | 395 | creator = CreatorFilter(fields.Str()) |
397 | 396 | service = ServiceFilter(fields.Str()) |
398 | 397 | severity = Filter(SeverityField()) |
399 | easeofresolution = Filter(fields.String( | |
400 | attribute='ease_of_resolution', | |
398 | ease_of_resolution = Filter(fields.String( | |
401 | 399 | validate=OneOf(Vulnerability.EASE_OF_RESOLUTIONS), |
402 | 400 | allow_none=True)) |
403 | pname = Filter(fields.String(attribute='parameter_name')) | |
404 | query = Filter(fields.String(attribute='query_string')) | |
405 | 401 | status_code = StatusCodeFilter(fields.Int()) |
406 | params = Filter(fields.String(attribute='parameters')) | |
407 | 402 | status = Filter(fields.Function( |
408 | 403 | deserialize=lambda val: 'open' if val == 'opened' else val, |
409 | 404 | validate=OneOf(Vulnerability.STATUSES + ['opened']) |
419 | 414 | # TODO migration: this can became a normal filter instead of a custom |
420 | 415 | # one, since now we can use creator_command_id |
421 | 416 | command_id = request.args.get('command_id') |
417 | ||
418 | # The web UI uses old field names. Translate them into the new field | |
419 | # names to maintain backwards compatiblity | |
420 | param_mapping = { | |
421 | 'query': 'query_string', | |
422 | 'pname': 'parameter_name', | |
423 | 'params': 'parameters', | |
424 | 'easeofresolution': 'ease_of_resolution', | |
425 | } | |
426 | new_args = request.args.copy() | |
427 | for (old_param, real_param) in param_mapping.items(): | |
428 | try: | |
429 | new_args[real_param] = request.args[old_param] | |
430 | except KeyError: | |
431 | pass | |
432 | request.args = ImmutableMultiDict(new_args) | |
433 | ||
422 | 434 | query = super(VulnerabilityFilterSet, self).filter() |
423 | 435 | |
424 | 436 | if command_id: |
725 | 737 | |
726 | 738 | normal_vulns = self.schema_class_dict['VulnerabilityWeb'](**marshmallow_params).dumps(normal_vulns.all(), |
727 | 739 | cls=BytesJSONEncoder) |
728 | normal_vulns_data = json.loads(normal_vulns.data) | |
740 | normal_vulns_data = json.loads(normal_vulns) | |
729 | 741 | except Exception as ex: |
730 | 742 | logger.exception(ex) |
731 | 743 | normal_vulns_data = [] |
742 | 754 | web_vulns = web_vulns.join(Service).join(Host).join(Hostname).filter(or_(*or_filters)) |
743 | 755 | web_vulns = self.schema_class_dict['VulnerabilityWeb'](**marshmallow_params).dumps(web_vulns.all(), |
744 | 756 | cls=BytesJSONEncoder) |
745 | web_vulns_data = json.loads(web_vulns.data) | |
757 | web_vulns_data = json.loads(web_vulns) | |
746 | 758 | except Exception as ex: |
747 | 759 | logger.exception(ex) |
748 | 760 | web_vulns_data = [] |
805 | 817 | object_id=vuln_id).all() |
806 | 818 | res = {} |
807 | 819 | for file_obj in files: |
808 | ret, errors = EvidenceSchema().dump(file_obj) | |
809 | if errors: | |
810 | raise ValidationError(errors, data=ret) | |
820 | ret = EvidenceSchema().dump(file_obj) | |
811 | 821 | res[file_obj.filename] = ret |
812 | 822 | |
813 | 823 | return flask.jsonify(res) |
53 | 53 | class WorkspaceSchema(AutoSchema): |
54 | 54 | |
55 | 55 | name = fields.String(required=True, |
56 | validate=validate.Regexp(r"^[a-z0-9][a-z0-9\_\$\(\)\+\-\/]*$", 0, "ERORROROR")) | |
56 | validate=validate.Regexp(r"^[a-z0-9][a-z0-9\_\$\(\)\+\-\/]*$", 0, error="ERORROROR")) | |
57 | 57 | stats = SelfNestedField(WorkspaceSummarySchema()) |
58 | 58 | duration = SelfNestedField(WorkspaceDurationSchema()) |
59 | 59 | _id = fields.Integer(dump_only=True, attribute='id') |
77 | 77 | 'create_date', 'update_date', 'readonly') |
78 | 78 | |
79 | 79 | @post_load |
80 | def post_load_duration(self, data): | |
80 | def post_load_duration(self, data, **kwargs): | |
81 | 81 | # Unflatten duration (move data[duration][*] to data[*]) |
82 | 82 | duration = data.pop('duration', None) |
83 | 83 | if duration: |
222 | 222 | |
223 | 223 | |
224 | 224 | WorkspaceView.register(workspace_api) |
225 | # I'm Py3⏎ | |
225 | # I'm Py3 |
168 | 168 | logger.warn('Invalid authentication token.') |
169 | 169 | flask.abort(401) |
170 | 170 | logged_in = True |
171 | flask.session['user_id'] = user.id | |
171 | flask.session['_user_id'] = user.id # TODO use public flask_login functions | |
172 | 172 | elif auth_type == 'agent': |
173 | 173 | # Don't handle the agent logic here, do it in another |
174 | 174 | # before_request handler |
177 | 177 | logger.warn("Invalid authorization type") |
178 | 178 | flask.abort(401) |
179 | 179 | else: |
180 | logged_in = 'user_id' in flask.session | |
181 | user_id = session.get("user_id") | |
180 | # TODO use public flask_login functions | |
181 | logged_in = '_user_id' in flask.session | |
182 | user_id = session.get("_user_id") | |
182 | 183 | if logged_in: |
183 | 184 | user = User.query.filter_by(id=user_id).first() |
184 | 185 | |
192 | 193 | if logged_in: |
193 | 194 | g.user = user |
194 | 195 | if user is None: |
195 | logger.warn("Unknown user id {}".format(session["user_id"])) | |
196 | del flask.session['user_id'] | |
196 | logger.warn("Unknown user id {}".format(session["_user_id"])) | |
197 | del flask.session['_user_id'] | |
197 | 198 | flask.abort(401) # 403 would be better but breaks the web ui |
198 | 199 | return |
199 | 200 |
7 | 7 | import time |
8 | 8 | import datetime |
9 | 9 | from flask import g |
10 | from marshmallow import fields, Schema, post_dump | |
10 | from marshmallow import fields, Schema, post_dump, EXCLUDE | |
11 | from marshmallow.utils import missing as missing_ | |
11 | 12 | from marshmallow.exceptions import ValidationError |
12 | from marshmallow.utils import missing | |
13 | 13 | from dateutil.tz import tzutc |
14 | 14 | |
15 | 15 | from faraday.server.models import ( |
27 | 27 | if value is not None: |
28 | 28 | return int(time.mktime(value.timetuple()) * 1000) |
29 | 29 | |
30 | def _deserialize(self, value, attr, data): | |
30 | def _deserialize(self, value, attr, data, **kwargs): | |
31 | 31 | if value is not None and value: |
32 | 32 | return datetime.datetime.fromtimestamp(self._validated(value)/1e3) |
33 | 33 | |
112 | 112 | return None |
113 | 113 | return getattr(value, self.field_name) |
114 | 114 | |
115 | def _deserialize(self, value, attr, data): | |
115 | def _deserialize(self, value, attr, data, **kwargs): | |
116 | 116 | raise NotImplementedError("Only dump is implemented for now") |
117 | 117 | |
118 | 118 | |
129 | 129 | super(SelfNestedField, self).__init__(*args, **kwargs) |
130 | 130 | |
131 | 131 | def _serialize(self, value, attr, obj): |
132 | ret, errors = self.target_schema.dump(obj) | |
133 | if errors: | |
134 | raise ValidationError(errors, data=ret) | |
135 | return ret | |
136 | ||
137 | def _deserialize(self, value, attr, data): | |
132 | return self.target_schema.dump(obj) | |
133 | ||
134 | def _deserialize(self, value, attr, data, **kwargs): | |
138 | 135 | """ |
139 | 136 | It would be awesome if this method could also flatten the dict keys into the parent |
140 | 137 | """ |
141 | load = self.target_schema.load(value) | |
142 | if load.errors: | |
143 | raise ValidationError(load.errors) | |
144 | ||
145 | return load.data | |
138 | return self.target_schema.load(value) | |
146 | 139 | |
147 | 140 | |
148 | 141 | class MutableField(fields.Field): |
171 | 164 | super(MutableField, self).__init__(**kwargs) |
172 | 165 | |
173 | 166 | def _serialize(self, value, attr, obj): |
167 | ||
168 | # TODO: see root cause of the bug that required this line to be added | |
169 | self.read_field.parent = self.parent | |
170 | ||
174 | 171 | return self.read_field._serialize(value, attr, obj) |
175 | 172 | |
176 | def _deserialize(self, value, attr, data): | |
177 | return self.write_field._deserialize(value, attr, data) | |
173 | def _deserialize(self, value, attr, data, **kwargs): | |
174 | ||
175 | # TODO: see root cause of the bug that required this line to be added | |
176 | self.write_field.parent = self.parent | |
177 | ||
178 | return self.write_field._deserialize(value, attr, data, **kwargs) | |
178 | 179 | |
179 | 180 | def _add_to_schema(self, field_name, schema): |
180 | 181 | # Propagate to child fields |
197 | 198 | return 'info' |
198 | 199 | return ret |
199 | 200 | |
200 | def _deserialize(self, value, attr, data): | |
201 | def _deserialize(self, value, attr, data, **kwargs): | |
201 | 202 | ret = super(SeverityField, self)._serialize(value, attr, data) |
202 | 203 | if ret == 'med': |
203 | 204 | return 'medium' |
222 | 223 | self.allow_none = True |
223 | 224 | self.default = '' |
224 | 225 | |
225 | def deserialize(self, value, attr=None, data=None): | |
226 | def deserialize(self, value, attr=None, data=None, **kwargs): | |
226 | 227 | # Validate required fields, deserialize, then validate |
227 | 228 | # deserialized value |
229 | self._validate_missing(value) | |
230 | if value is missing_: | |
231 | _miss = self.missing | |
232 | return _miss() if callable(_miss) else _miss | |
228 | 233 | if isinstance(value, str): |
229 | 234 | value = value.replace('\0', '') # Postgres does not allow nul 0x00 in the strings. |
230 | elif value is not None and value != missing: | |
235 | elif value is not None: | |
231 | 236 | raise ValidationError("Deserializing a non string field when expected") |
232 | self._validate_missing(value) | |
233 | 237 | if getattr(self, 'allow_none', False) is True and value is None: |
234 | 238 | return '' |
235 | output = self._deserialize(value, attr, data) | |
239 | output = self._deserialize(value, attr, data, **kwargs) | |
236 | 240 | self._validate(output) |
237 | 241 | return output |
238 | 242 | |
250 | 254 | update_action = fields.Integer(default=0, dump_only=True) |
251 | 255 | update_controller_action = fields.String(default='', dump_only=True) |
252 | 256 | |
257 | class Meta: | |
258 | unknown = EXCLUDE | |
259 | ||
253 | 260 | |
254 | 261 | class StrictDateTimeField(fields.DateTime): |
255 | 262 | """ |
265 | 272 | super(StrictDateTimeField, self).__init__(*args, **kwargs) |
266 | 273 | self.load_as_tz_aware = load_as_tz_aware |
267 | 274 | |
268 | def _deserialize(self, value, attr, data): | |
275 | def _deserialize(self, value, attr, data, **kwargs): | |
269 | 276 | if isinstance(value, datetime.datetime): |
270 | 277 | date = value |
271 | 278 | else: |
333 | 340 | return '{}={}'.format(object_rule_name, value) |
334 | 341 | |
335 | 342 | @post_dump |
336 | def remove_none_values(self, data): | |
343 | def remove_none_values(self, data, **kwargs): | |
337 | 344 | actions = [] |
338 | 345 | conditions = [] |
339 | 346 | for action in data['actions']: |
349 | 356 | if value |
350 | 357 | } |
351 | 358 | |
352 | # I'm Py3⏎ | |
359 | # I'm Py3 |
31 | 31 | from faraday.server.web import app # pylint:disable=import-outside-toplevel |
32 | 32 | with app.app_context(): |
33 | 33 | ws = Workspace.query.filter_by(name=workspace_name).one() |
34 | schema = BulkCreateSchema(strict=True) | |
35 | data = schema.load(report_json).data | |
34 | schema = BulkCreateSchema() | |
35 | data = schema.load(report_json) | |
36 | 36 | data = add_creator(data, user) |
37 | 37 | bulk_create(ws, data, True) |
38 | 38 | |
74 | 74 | self.stop() |
75 | 75 | except Exception as ex: |
76 | 76 | logger.exception(ex) |
77 | continue⏎ | |
77 | continue |
0 | from functools import wraps | |
1 | from flask import request | |
2 | from werkzeug.contrib.cache import SimpleCache | |
3 | ||
4 | cache = SimpleCache() | |
5 | ||
6 | ||
7 | def cached(timeout=5 * 60, key='view/%s'): | |
8 | def decorator(f): | |
9 | @wraps(f) | |
10 | def decorated_function(*args, **kwargs): | |
11 | cache_key = key % request.path | |
12 | rv = cache.get(cache_key) | |
13 | if rv is not None: | |
14 | return rv | |
15 | rv = f(*args, **kwargs) | |
16 | cache.set(cache_key, rv, timeout=timeout) | |
17 | return rv | |
18 | return decorated_function | |
19 | return decorator | |
20 | # I'm Py3⏎ |
84 | 84 | <br><br><br><br> |
85 | 85 | <p>This feature belongs to our commercial versions. |
86 | 86 | <br> |
87 | For more information, please contact us at <span class="bold">[email protected]</span> or visit <a href="http://www.faradaysec.com">www.faradaysec.com</a>!</p> | |
87 | For more information, please contact us at <span class="bold">[email protected]</span> or visit <a href="https://www.faradaysec.com">www.faradaysec.com</a>!</p> | |
88 | 88 | </div><!-- .jumbotron --> |
89 | 89 | </div><!-- #reports-main --></div><!-- .right-main --> |
90 | 90 | </section><!-- #main --> |
5 | 5 | <h4 class="modal-title"><img src="/images/faraday-iso.svg" height="30" style="display: inline; padding: 5px; vertical-align:middle;"> About Faraday</h4> |
6 | 6 | </div> |
7 | 7 | <div class="modal-body"> |
8 | <h2>Faraday <b>{{version}}</b> <small>by <a href="http://infobytesec.com">Infobyte Security</a></small></h2> | |
8 | <h2>Faraday <b>{{version}}</b> <small>by <a href="https://faradaysec.com">FaradaySEC</a></small></h2> | |
9 | 9 | <br/> |
10 | 10 | <h5>The collaborative penetration test environment the world needed!</h5> |
11 | 11 | </div><!-- .modal-body --> |
19 | 19 | <tr ng-repeat="host in hosts | limitTo:5"> |
20 | 20 | <td class=""> |
21 | 21 | <a href="" class="host" ng-click="showServices(host)">{{host.name}}</a> |
22 | <osint-link query="host.name" osint="osint"></osint-link> | |
22 | <a href="//www.shodan.io/search?query={{host.ip}}" uib-tooltip="Search in shodan" target="_blank"> | |
23 | <img ng-src="/images/shodan.png" height="15px" width="15px" /> | |
24 | </a> | |
23 | 25 | </td> |
24 | 26 | <td class="">{{host.services}}</td> |
25 | 27 | <td class=""> |
183 | 183 | <td class="ui-grid-cell-contents" |
184 | 184 | ng-show="columns['search_in_shodan']"> |
185 | 185 | <div class=""> |
186 | <osint-link query="host.ip" osint="osint"></osint-link> | |
186 | <a href="//www.shodan.io/search?query={{host.ip}}" uib-tooltip="Search in shodan" target="_blank"> | |
187 | <img ng-src="/images/shodan.png" height="15px" width="15px" /> | |
188 | </a> | |
187 | 189 | </div> |
188 | 190 | </td> |
189 | 191 | <td class="ui-grid-cell-contents" |
5 | 5 | |
6 | 6 | nixpkgs = builtins.fetchTarball { |
7 | 7 | url = |
8 | "https://github.com/nixos/nixpkgs/archive/91c43a9dc822da30cf3cd2908891edddcea482f2.tar.gz"; | |
9 | sha256 = "06ikg56ifx57b0n9yqa9szipbsswn98gz5zszi8nsqd7d4p4l3y4"; | |
8 | "https://github.com/infobyte/nixpkgs/archive/3b9c8522a0c61980f03f0e120f628755af720f3c.tar.gz"; | |
9 | sha256 = "1iq1mzikhfn8sjh2a9gx8sy7z4ncf0m9n0k6w9yxzh48ygfpca16"; | |
10 | 10 | }; |
11 | 11 | |
12 | 12 | packageOverrides = self: super: { |
13 | 13 | |
14 | 14 | faradaysec = self.callPackage ./packages/faradaysec.nix { }; |
15 | 15 | |
16 | flask = self.callPackage ./packages/flask.nix { }; | |
16 | werkzeug = self.callPackage ./packages/werkzeug.nix { }; | |
17 | 17 | |
18 | 18 | flask-classful = self.callPackage ./packages/flask-classful.nix { }; |
19 | 19 | |
20 | flask-login = self.callPackage ./packages/flask-login.nix { }; | |
21 | ||
20 | 22 | flask-security = self.callPackage ./packages/flask-security.nix { }; |
21 | 23 | |
22 | flask-babelex = self.callPackage ./packages/flask-babelex.nix { }; | |
23 | ||
24 | pgcli = self.callPackage ./packages/pgcli.nix { }; | |
25 | ||
26 | 24 | webargs = self.callPackage ./packages/webargs.nix { }; |
27 | ||
28 | marshmallow-sqlalchemy = | |
29 | self.callPackage ./packages/marshmallow-sqlalchemy.nix { }; | |
30 | 25 | |
31 | 26 | filteralchemy-fork = self.callPackage ./packages/filteralchemy-fork.nix { }; |
32 | 27 |
0 | 0 | { alembic, apispec, apispec-webframeworks, autobahn, bcrypt, buildPythonPackage |
1 | , click, colorama, dateutil, distro, faraday-plugins, fetchPypi, filedepot | |
2 | , filteralchemy-fork, flask, flask-classful, flask-kvsession-fork | |
3 | , flask-restless, flask-security, flask_login, flask_sqlalchemy, lib | |
1 | , click, colorama, dateutil, distro, email_validator, faraday-plugins, fetchPypi | |
2 | , filedepot, filteralchemy-fork, flask, flask-classful, flask-kvsession-fork | |
3 | , flask-login, flask-restless, flask-security, flask_sqlalchemy, lib | |
4 | 4 | , marshmallow, marshmallow-sqlalchemy, nplusone, pgcli, pillow, psycopg2, pyasn1 |
5 | 5 | , pyopenssl, pytestrunner, requests, service-identity, simplejson, simplekv |
6 | , sqlalchemy, syslog-rfc5424-formatter, tqdm, twisted, webargs, werkzeug }: | |
6 | , sqlalchemy, syslog-rfc5424-formatter, tqdm, twisted, webargs, werkzeug | |
7 | , wtforms }: | |
7 | 8 | buildPythonPackage rec { |
8 | 9 | pname = "faradaysec"; |
9 | 10 | version = "0.1dev"; |
24 | 25 | flask |
25 | 26 | flask_sqlalchemy |
26 | 27 | flask-classful |
27 | flask_login | |
28 | email_validator | |
29 | wtforms | |
30 | flask-login | |
28 | 31 | flask-security |
29 | 32 | marshmallow |
30 | 33 | pillow |
0 | { Babel, buildPythonPackage, fetchPypi, flask, jinja2, lib, speaklater }: | |
1 | buildPythonPackage rec { | |
2 | pname = "flask-babelex"; | |
3 | version = "0.9.4"; | |
4 | ||
5 | src = builtins.fetchurl { | |
6 | url = | |
7 | "https://files.pythonhosted.org/packages/85/e7/217fb37ccd4bd93cd0f002028fb7c5fdf6ee0063a6beb83e43cd903da46e/Flask-BabelEx-0.9.4.tar.gz"; | |
8 | sha256 = "09yfr8hlwvpgvq8kp1y7qbnnl0q28hi0348bv199ssiqx779r99r"; | |
9 | }; | |
10 | ||
11 | # TODO FIXME | |
12 | doCheck = false; | |
13 | ||
14 | buildInputs = [ ]; | |
15 | propagatedBuildInputs = [ flask Babel speaklater jinja2 ]; | |
16 | } |
0 | { buildPythonPackage, fetchPypi, flask, lib }: | |
1 | buildPythonPackage rec { | |
2 | pname = "flask-login"; | |
3 | version = "0.5.0"; | |
4 | ||
5 | src = builtins.fetchurl { | |
6 | url = | |
7 | "https://files.pythonhosted.org/packages/f9/01/f6c0a3a654ca125cf9cd273314c03a8bc6a47bf861765c8c1d375e15a28d/Flask-Login-0.5.0.tar.gz"; | |
8 | sha256 = "0jqb3jfm92yyz4f8n3f92f7y59p8m9j98cyc19wavkjvbgqswcvd"; | |
9 | }; | |
10 | ||
11 | # TODO FIXME | |
12 | doCheck = false; | |
13 | ||
14 | buildInputs = [ ]; | |
15 | propagatedBuildInputs = [ flask ]; | |
16 | } |
0 | { Babel, buildPythonPackage, fetchPypi, flask, flask-babelex, flask_login | |
0 | { Babel, buildPythonPackage, fetchPypi, flask, flask-babelex, flask-login | |
1 | 1 | , flask_mail, flask_principal, flask_wtf, itsdangerous, lib, passlib |
2 | 2 | , pytestrunner }: |
3 | 3 | buildPythonPackage rec { |
16 | 16 | buildInputs = [ Babel pytestrunner ]; |
17 | 17 | propagatedBuildInputs = [ |
18 | 18 | flask |
19 | flask_login | |
19 | flask-login | |
20 | 20 | flask_mail |
21 | 21 | flask_principal |
22 | 22 | flask_wtf |
0 | { buildPythonPackage, click, fetchPypi, itsdangerous, jinja2, lib, werkzeug }: | |
1 | buildPythonPackage rec { | |
2 | pname = "flask"; | |
3 | version = "1.1.2"; | |
4 | ||
5 | src = builtins.fetchurl { | |
6 | url = | |
7 | "https://files.pythonhosted.org/packages/4e/0b/cb02268c90e67545a0e3a37ea1ca3d45de3aca43ceb7dbf1712fb5127d5d/Flask-1.1.2.tar.gz"; | |
8 | sha256 = "0q3h295izcil7lswkzfnyg3k5gq4hpmqmpl6i7s5m1n9szi1myjf"; | |
9 | }; | |
10 | ||
11 | # TODO FIXME | |
12 | doCheck = false; | |
13 | ||
14 | buildInputs = [ ]; | |
15 | propagatedBuildInputs = [ werkzeug jinja2 itsdangerous click ]; | |
16 | } |
0 | { buildPythonPackage, fetchPypi, lib, marshmallow, sqlalchemy }: | |
1 | buildPythonPackage rec { | |
2 | pname = "marshmallow-sqlalchemy"; | |
3 | version = "0.15.0"; | |
4 | ||
5 | src = builtins.fetchurl { | |
6 | url = | |
7 | "https://files.pythonhosted.org/packages/fe/d2/de4f83721cddc2f4f9525efe916c4e87d54ca00aa678098d9d5bcdfcf966/marshmallow-sqlalchemy-0.15.0.tar.gz"; | |
8 | sha256 = "1phqbbrq1xjvc7cwasy5zws4bdb050qikfp1qg8f1hqhmipkpiaz"; | |
9 | }; | |
10 | ||
11 | # TODO FIXME | |
12 | doCheck = false; | |
13 | ||
14 | buildInputs = [ ]; | |
15 | propagatedBuildInputs = [ marshmallow sqlalchemy ]; | |
16 | } |
0 | { buildPythonPackage, cli-helpers, click, configobj, fetchPypi, humanize, lib | |
1 | , pgspecial, prompt_toolkit, psycopg2, pygments, setproctitle, sqlparse }: | |
2 | buildPythonPackage rec { | |
3 | pname = "pgcli"; | |
4 | version = "2.1.0"; | |
5 | ||
6 | src = builtins.fetchurl { | |
7 | url = | |
8 | "https://files.pythonhosted.org/packages/ed/90/c8d33a8be3d85347a23ccd5663b8a2e82f6c79b75eb2fd9339371a9f1284/pgcli-2.1.0.tar.gz"; | |
9 | sha256 = "0p60297ppljc2nyqfchzcc17ls4m5841i7gyzqags0j8fg3s749p"; | |
10 | }; | |
11 | ||
12 | # TODO FIXME | |
13 | doCheck = false; | |
14 | ||
15 | buildInputs = [ ]; | |
16 | propagatedBuildInputs = [ | |
17 | pgspecial | |
18 | click | |
19 | pygments | |
20 | prompt_toolkit | |
21 | psycopg2 | |
22 | sqlparse | |
23 | configobj | |
24 | humanize | |
25 | cli-helpers | |
26 | setproctitle | |
27 | ]; | |
28 | } |
0 | 0 | { buildPythonPackage, fetchPypi, lib }: |
1 | 1 | buildPythonPackage rec { |
2 | 2 | pname = "simplekv"; |
3 | version = "0.13.0"; | |
3 | version = "0.14.1"; | |
4 | 4 | |
5 | 5 | src = builtins.fetchurl { |
6 | 6 | url = |
7 | "https://files.pythonhosted.org/packages/42/8e/4f96c4038d966bafbe020c36770599ce4e0f0ccbb7b93437d7742a952e03/simplekv-0.13.0.tar.gz"; | |
8 | sha256 = "01iw920m8aaak3dp0y61ny7vin5yizm55h9i2vwgkv0qhvsfhlmf"; | |
7 | "https://files.pythonhosted.org/packages/30/6f/a6cafd4e87757e316468bf56287806b8df8ad4505f6da449a507e8cbacee/simplekv-0.14.1.tar.gz"; | |
8 | sha256 = "1xnh5k7bhvi6almfsv3zj8dzxxiv66sn46fyr4hsh7klndna6lw9"; | |
9 | 9 | }; |
10 | 10 | |
11 | 11 | # TODO FIXME |
0 | 0 | { buildPythonPackage, fetchPypi, lib, marshmallow }: |
1 | 1 | buildPythonPackage rec { |
2 | 2 | pname = "webargs"; |
3 | version = "5.5.3"; | |
3 | version = "6.1.0"; | |
4 | 4 | |
5 | 5 | src = builtins.fetchurl { |
6 | 6 | url = |
7 | "https://files.pythonhosted.org/packages/5a/46/72d3c7e0acbdb9c79caf7e03835cd7f77163026811855b59a1eaf6c0c2e5/webargs-5.5.3.tar.gz"; | |
8 | sha256 = "16pjzc265yx579ijz5scffyfd1vsmi87fdcgnzaj2by6w2i445l7"; | |
7 | "https://files.pythonhosted.org/packages/c0/05/787ada84c00f52636327b09368ff0212861ebf44365e799126cedca20303/webargs-6.1.0.tar.gz"; | |
8 | sha256 = "0gxvd1k5czch2l3jpvgbb53wbzl2drld25rs45jcfkrwbjrpzd7b"; | |
9 | 9 | }; |
10 | 10 | |
11 | 11 | # TODO FIXME |
0 | { buildPythonPackage, fetchPypi, lib }: | |
1 | buildPythonPackage rec { | |
2 | pname = "werkzeug"; | |
3 | version = "1.0.1"; | |
4 | ||
5 | src = builtins.fetchurl { | |
6 | url = | |
7 | "https://files.pythonhosted.org/packages/10/27/a33329150147594eff0ea4c33c2036c0eadd933141055be0ff911f7f8d04/Werkzeug-1.0.1.tar.gz"; | |
8 | sha256 = "0z74sa1xw5h20yin9faj0vvdbq713cgbj84klc72jr9nmpjv303c"; | |
9 | }; | |
10 | ||
11 | # TODO FIXME | |
12 | doCheck = false; | |
13 | ||
14 | buildInputs = [ ]; | |
15 | propagatedBuildInputs = [ ]; | |
16 | } |
0 | werkzeug<1.0.0 | |
0 | werkzeug>=1.0.0 | |
1 | 1 | autobahn>=17.10.1 |
2 | 2 | alembic>=0.9.9 |
3 | 3 | bcrypt>=3.1.4 |
6 | 6 | flask>=1.1 |
7 | 7 | Flask-SQLAlchemy>=2.3.1 |
8 | 8 | flask-classful>=0.14 |
9 | flask-login<0.5.0 | |
9 | email_validator | |
10 | WTForms>=2.1 | |
11 | flask-login>=0.5.0 | |
10 | 12 | Flask-Security>=3.0.0 |
11 | marshmallow<3.0.0 | |
13 | marshmallow>=3.0.0 | |
12 | 14 | Pillow>=4.2.1 |
13 | psycopg2==2.7.7 | |
14 | pgcli==2.1.0 | |
15 | psycopg2 | |
16 | pgcli | |
15 | 17 | pyopenssl>=17.2.0 |
16 | 18 | python-dateutil>=2.6.0 |
17 | 19 | requests>=2.18.4 |
20 | 22 | SQLAlchemy>=1.2.0b2 |
21 | 23 | tqdm>=4.15.0 |
22 | 24 | twisted>=18.9.0 |
23 | webargs>=5.1.0,<6.0.0 | |
24 | marshmallow-sqlalchemy==0.15.0 | |
25 | webargs>=6.0.0 | |
26 | marshmallow-sqlalchemy | |
25 | 27 | filteralchemy-fork |
26 | 28 | filedepot>=0.5.0 |
27 | 29 | nplusone>=0.8.1 |
28 | Flask-Restless==0.17.0 | |
30 | Flask-Restless>=0.17.0 | |
29 | 31 | simplejson>=3.16.0 |
30 | 32 | syslog-rfc5424-formatter>=1.1.1 |
31 | simplekv==0.13.0 | |
33 | simplekv>=0.13.0 | |
32 | 34 | Flask-KVSession-fork>=0.6.3 |
33 | 35 | distro>=1.4.0 |
34 | 36 | faraday-plugins>=1.0.1,<2.0.0 |
270 | 270 | # http://pythonhosted.org/Flask-Testing/#testing-with-sqlalchemy |
271 | 271 | assert user.id is not None |
272 | 272 | db.session.add(user) |
273 | sess['user_id'] = user.id | |
273 | sess['_user_id'] = user.id # TODO use public flask_login functions | |
274 | 274 | identity_changed.send(test_client.application, |
275 | 275 | identity=Identity(user.id)) |
276 | 276 |
57 | 57 | .format(ws_name=ws.name, id=command.id), |
58 | 58 | data=data, |
59 | 59 | ) |
60 | assert res.status_code == 200 | |
60 | 61 | |
61 | 62 | # Changing res.json['itime'] to timestamp format of itime |
62 | 63 | res_itime = res.json['itime'] / 1000.0 |
63 | 64 | assert res.status_code == 200 |
64 | 65 | assert res_itime == itime |
65 | ||
66 | ||
67 | # I'm Py3 |
37 | 37 | 'availability': False, |
38 | 38 | }, |
39 | 39 | 'refs': ['CVE-1234'], |
40 | 'tool': 'some_tool' | |
40 | 'tool': 'some_tool', | |
41 | 'data': 'test data', | |
41 | 42 | } |
42 | 43 | |
43 | 44 | vuln_web_data = { |
113 | 114 | |
114 | 115 | |
115 | 116 | def test_create_service(session, host): |
116 | data = bc.BulkServiceSchema(strict=True).load(service_data).data | |
117 | data = bc.BulkServiceSchema(strict=True).load(service_data) | |
117 | 118 | bc._create_service(host.workspace, host, data) |
118 | 119 | assert count(Service, host.workspace) == 1 |
119 | 120 | service = Service.query.filter(Service.workspace == host.workspace).one() |
129 | 130 | "port": service.port, |
130 | 131 | "protocol": service.protocol, |
131 | 132 | } |
132 | data = bc.BulkServiceSchema(strict=True).load(data).data | |
133 | data = bc.BulkServiceSchema(strict=True).load(data) | |
133 | 134 | bc._create_service(service.workspace, service.host, data) |
134 | 135 | assert count(Service, service.host.workspace) == 1 |
135 | 136 | |
136 | 137 | def test_create_host_vuln(session, host): |
137 | data = bc.VulnerabilitySchema(strict=True).load(vuln_data).data | |
138 | data = bc.VulnerabilitySchema(strict=True).load(vuln_data) | |
138 | 139 | bc._create_hostvuln(host.workspace, host, data) |
139 | 140 | assert count(VulnerabilityGeneric, host.workspace) == 1 |
140 | 141 | assert count(Vulnerability, host.workspace) == 1 |
150 | 151 | |
151 | 152 | |
152 | 153 | def test_create_service_vuln(session, service): |
153 | data = bc.VulnerabilitySchema(strict=True).load(vuln_data).data | |
154 | data = bc.VulnerabilitySchema(strict=True).load(vuln_data) | |
154 | 155 | bc._create_servicevuln(service.workspace, service, data) |
155 | 156 | assert count(VulnerabilityGeneric, service.workspace) == 1 |
156 | 157 | assert count(Vulnerability, service.workspace) == 1 |
169 | 170 | def test_create_host_vuln_without_tool(session, host): |
170 | 171 | no_tool_data = vuln_data.copy() |
171 | 172 | no_tool_data.pop('tool') |
172 | data = bc.VulnerabilitySchema(strict=True).load(no_tool_data).data | |
173 | data = bc.VulnerabilitySchema(strict=True).load(no_tool_data) | |
173 | 174 | bc._create_hostvuln(host.workspace, host, data) |
174 | 175 | vuln = host.workspace.vulnerabilities[0] |
175 | 176 | assert vuln.tool == "Web UI" |
222 | 223 | 'type': 'Vulnerability', |
223 | 224 | 'refs': ['new'] |
224 | 225 | } |
225 | data = bc.VulnerabilitySchema(strict=True).load(data).data | |
226 | data = bc.VulnerabilitySchema(strict=True).load(data) | |
226 | 227 | bc._create_hostvuln(host.workspace, host, data) |
227 | 228 | session.commit() |
228 | 229 | assert count(Vulnerability, host.workspace) == 1 |
276 | 277 | def test_create_service_with_vuln(session, host): |
277 | 278 | service_data_ = service_data.copy() |
278 | 279 | service_data_['vulnerabilities'] = [vuln_data] |
279 | data = bc.BulkServiceSchema(strict=True).load(service_data_).data | |
280 | data = bc.BulkServiceSchema(strict=True).load(service_data_) | |
280 | 281 | bc._create_service(host.workspace, host, data) |
281 | 282 | assert count(Service, host.workspace) == 1 |
282 | 283 | service = host.workspace.services[0] |
290 | 291 | def test_create_service_with_cred(session, host): |
291 | 292 | service_data_ = service_data.copy() |
292 | 293 | service_data_['credentials'] = [credential_data] |
293 | data = bc.BulkServiceSchema(strict=True).load(service_data_).data | |
294 | data = bc.BulkServiceSchema(strict=True).load(service_data_) | |
294 | 295 | bc._create_service(host.workspace, host, data) |
295 | 296 | assert count(Service, host.workspace) == 1 |
296 | 297 | service = host.workspace.services[0] |
309 | 310 | del vuln_data_['name'] |
310 | 311 | service_data_['vulnerabilities'] = [vuln_data_] |
311 | 312 | with pytest.raises(ValidationError): |
312 | data = bc.BulkServiceSchema(strict=True).load(service_data_).data | |
313 | data = bc.BulkServiceSchema(strict=True).load(service_data_) | |
313 | 314 | bc._create_service(host.workspace, host, data) |
314 | 315 | assert count(Service, host.workspace) == 0 |
315 | 316 | assert count(Vulnerability, host.workspace) == 0 |
321 | 322 | del vuln_data_['name'] |
322 | 323 | service_data_['vulnerabilities'] = [1, 2, 3] |
323 | 324 | with pytest.raises(ValidationError): |
324 | data = bc.BulkServiceSchema(strict=True).load(service_data_).data | |
325 | data = bc.BulkServiceSchema(strict=True).load(service_data_) | |
325 | 326 | bc._create_service(host.workspace, host, data) |
326 | 327 | assert count(Service, host.workspace) == 0 |
327 | 328 | assert count(Vulnerability, host.workspace) == 0 |
332 | 333 | vuln_data_ = vuln_data.copy() |
333 | 334 | vuln_data_.update(vuln_web_data) |
334 | 335 | service_data_['vulnerabilities'] = [vuln_data_] |
335 | data = bc.BulkServiceSchema(strict=True).load(service_data_).data | |
336 | data = bc.BulkServiceSchema(strict=True).load(service_data_) | |
336 | 337 | bc._create_service(host.workspace, host, data) |
337 | 338 | assert count(Service, host.workspace) == 1 |
338 | 339 | service = host.workspace.services[0] |
372 | 372 | def test_update_command(self, test_client, session): |
373 | 373 | command = self.factory() |
374 | 374 | session.commit() |
375 | raw_data ={ | |
375 | raw_data = { | |
376 | 376 | 'command': 'Import Nessus:', |
377 | 377 | 'tool': 'nessus', |
378 | 378 | 'duration': 120, |
383 | 383 | 'user': 'lcubo' |
384 | 384 | } |
385 | 385 | |
386 | res = test_client.put(self.url(command, workspace=command.workspace), data=raw_data) | |
386 | res = test_client.put(self.url(command, workspace=command.workspace), | |
387 | data=raw_data) | |
387 | 388 | assert res.status_code == 200 |
388 | 389 | updated_command = self.model.query.get(command.id) |
389 | assert updated_command.end_date == datetime.datetime.fromtimestamp(1511387720.048548) + datetime.timedelta(seconds=120) | |
390 | print(updated_command.end_date) | |
391 | assert updated_command.end_date == datetime.datetime.fromtimestamp( | |
392 | 1511387720.048548) + datetime.timedelta(seconds=120) | |
390 | 393 | |
391 | 394 | def test_delete_objects_preserve_history(self, session, test_client): |
392 | 395 |
56 | 56 | raw_comment = self._create_raw_comment('workspace', service.id) |
57 | 57 | res = test_client.post(self.url(), data=raw_comment) |
58 | 58 | assert res.status_code == 400 |
59 | assert res.json == {u'messages': {u'object_type': [u'Not a valid choice.']}} | |
59 | assert 'Must be one of' in res.json['messages']['json']['object_type'][0] | |
60 | 60 | |
61 | 61 | def test_cannot_create_comment_of_another_workspace_object(self, test_client, session, second_workspace): |
62 | 62 | service = ServiceFactory.create(workspace=self.workspace) |
166 | 166 | } |
167 | 167 | res = test_client.post(self.url(), data=raw_data) |
168 | 168 | assert res.status_code == 400 |
169 | assert res.json['messages']['_schema'] == ['Unknown parent type: Vulnerability'] | |
169 | assert res.json['messages']['json']['_schema'] == ['Unknown parent type: Vulnerability'] | |
170 | 170 | |
171 | 171 | |
172 | 172 | def test_update_credentials(self, test_client, session, host): |
87 | 87 | } |
88 | 88 | res = test_client.post(self.url(), data=data) |
89 | 89 | assert res.status_code == 400 |
90 | assert b'Not a valid choice' in res.data | |
90 | assert b'Must be one of' in res.data | |
91 | 91 | |
92 | 92 | def test_create_fails_with_no_host_id(self, test_client, |
93 | 93 | host, session): |
207 | 207 | session.commit() |
208 | 208 | raw_data = self._raw_put_data(service.id, parent=service.host.id, status='closed') |
209 | 209 | res = test_client.put(self.url(service, workspace=service.workspace), data=raw_data) |
210 | assert res.status_code == 200 | |
210 | assert res.status_code == 200, res.json | |
211 | 211 | updated_service = Service.query.filter_by(id=service.id).first() |
212 | 212 | assert updated_service.status == 'closed' |
213 | 213 | |
216 | 216 | session.commit() |
217 | 217 | raw_data = self._raw_put_data(service.id, parent=service.host.id, ports=[221]) |
218 | 218 | res = test_client.put(self.url(service, workspace=service.workspace), data=raw_data) |
219 | assert res.status_code == 200 | |
219 | assert res.status_code == 200, res.json | |
220 | 220 | updated_service = Service.query.filter_by(id=service.id).first() |
221 | 221 | assert updated_service.port == 221 |
222 | 222 | |
226 | 226 | session.commit() |
227 | 227 | raw_data = self._raw_put_data(service.id) |
228 | 228 | res = test_client.put(self.url(service, workspace=service.workspace), data=raw_data) |
229 | assert res.status_code == 200 | |
229 | assert res.status_code == 200, res.json | |
230 | 230 | assert res.json['id'] == service.id |
231 | 231 | |
232 | 232 | def test_create_service_from_command(self, test_client, session): |
265 | 265 | } |
266 | 266 | res = test_client.post(self.url(), data=data) |
267 | 267 | assert res.status_code == 400 |
268 | assert res.json['messages']['_schema'] == res.json['messages']['_schema'] | |
269 | 268 | |
270 | 269 | def test_load_ports_without_list(self, test_client): |
271 | 270 | data = { |
660 | 660 | assert res.json['method'] == 'GET' |
661 | 661 | assert res.json['path'] == '/pepep' |
662 | 662 | |
663 | ||
664 | ||
665 | @pytest.mark.parametrize('param_name', ['query', 'query_string']) | |
666 | @pytest.mark.usefixtures('mock_envelope_list') | |
667 | def test_filter_by_querystring( | |
668 | self, test_client, session, second_workspace, | |
669 | vulnerability_web_factory, param_name): | |
670 | # VulnerabilityFilterSet has duplicate fields with the same function. | |
671 | # This was designed to maintain backwards compatibility | |
672 | ||
673 | VulnerabilityGeneric.query.delete() | |
674 | ||
675 | # Vulns that shouldn't be shown | |
676 | not_expected = vulnerability_web_factory.create_batch( | |
677 | 5, workspace=second_workspace, query_string='aaa') | |
678 | ||
679 | # Vulns that must be shown | |
680 | expected_vulns = vulnerability_web_factory.create_batch( | |
681 | 5, workspace=second_workspace, query_string='bbb') | |
682 | session.add_all(not_expected) | |
683 | session.add_all(expected_vulns) | |
684 | session.commit() | |
685 | expected_ids = {vuln.id for vuln in expected_vulns} | |
686 | ||
687 | res = test_client.get( | |
688 | self.url(workspace=second_workspace) + f'?{param_name}=bbb') | |
689 | assert res.status_code == 200 | |
690 | ||
691 | for vuln in res.json['data']: | |
692 | assert vuln['query'] == 'bbb' | |
693 | assert set(vuln['_id'] for vuln in res.json['data']) == expected_ids | |
694 | ||
695 | ||
663 | 696 | @pytest.mark.usefixtures('mock_envelope_list') |
664 | 697 | @pytest.mark.parametrize('medium_name', ['medium', 'med']) |
665 | 698 | def test_filter_by_severity(self, test_client, session, |
1022 | 1055 | res = test_client.post('/v2/ws/{0}/vulns/'.format(ws_name), data=raw_data) |
1023 | 1056 | assert res.status_code == 400 |
1024 | 1057 | assert vuln_count_previous == session.query(Vulnerability).count() |
1025 | assert list(res.json['messages'].keys()) == ['easeofresolution'] | |
1026 | assert res.json['messages']['easeofresolution'][0] == u'Not a valid choice.' | |
1058 | assert list(res.json['messages']['json'].keys()) == ['easeofresolution'] | |
1059 | assert 'Must be one of' in res.json['messages']['json']['easeofresolution'][0] | |
1027 | 1060 | |
1028 | 1061 | def test_create_vuln_with_null_ease_of_resolution(self, |
1029 | 1062 | host_with_hostnames, |
1552 | 1585 | 'Host', |
1553 | 1586 | ) |
1554 | 1587 | res = test_client.put(self.url(obj=target_vuln), data=raw_data) |
1555 | assert res.status_code == 409 | |
1588 | assert res.status_code == 409, res.json | |
1556 | 1589 | |
1557 | 1590 | def test_create_and_update_webvuln(self, host_with_hostnames, test_client, session): |
1558 | 1591 | """ |
1686 | 1719 | |
1687 | 1720 | res = test_client.post(self.url(), data=raw_data) |
1688 | 1721 | assert res.status_code == 400 |
1689 | assert res.json == {u'messages': {u'_schema': [u'Parent id not found: 358302']}} | |
1722 | assert res.json == {u'messages': {'json': {u'_schema': [u'Parent id not found: 358302']}}} | |
1690 | 1723 | |
1691 | 1724 | def test_after_deleting_vuln_ref_and_policies_remains(self, session, test_client): |
1692 | 1725 | vuln = VulnerabilityFactory.create(workspace=self.workspace) |
1792 | 1825 | 'parent_type': 'Host', |
1793 | 1826 | 'parent': host.id, |
1794 | 1827 | 'type': 'Vulnerability', |
1795 | 'refs': '' | |
1796 | 1828 | } |
1797 | 1829 | res = test_client.post(self.url(), data=data) |
1798 | 1830 | assert res.status_code == 201 |
2017 | 2049 | policyviolations=[], |
2018 | 2050 | ) |
2019 | 2051 | res = test_client.post(self.url(), data=raw_data) |
2020 | assert res.json['messages']['_schema'][0] == 'Unknown parent type' | |
2052 | assert res.json['messages']['json']['_schema'][0] == 'Unknown parent type' | |
2021 | 2053 | |
2022 | 2054 | def test_add_empty_attachment(self, test_client, session, workspace, csrf_token): |
2023 | 2055 | vuln = VulnerabilityFactory.create(workspace=workspace) |
233 | 233 | workspace=self.first_object.workspace)) |
234 | 234 | |
235 | 235 | session.commit() |
236 | res = test_client.get(self.url() + "count/?group_by=creator_id").get_json() | |
236 | res = test_client.get(self.url() + "count/?group_by=creator_id") | |
237 | assert res.status_code == 200, res.json | |
238 | res = res.get_json() | |
237 | 239 | |
238 | 240 | creators = [] |
239 | 241 | grouped = 0 |
251 | 253 | workspace=self.first_object.workspace)) |
252 | 254 | |
253 | 255 | session.commit() |
254 | res = test_client.get(self.url() + "count/?group_by=creator_id&order=desc").get_json() | |
256 | res = test_client.get(self.url() + "count/?group_by=creator_id&order=desc") | |
257 | assert res.status_code == 200, res.json | |
258 | res = res.get_json() | |
255 | 259 | |
256 | 260 | creators = [] |
257 | 261 | grouped = 0 |
12 | 12 | from marshmallow import Schema, fields, ValidationError |
13 | 13 | from faraday.server.schemas import ( |
14 | 14 | JSTimestampField, |
15 | NullToBlankString, | |
15 | 16 | MutableField, |
16 | 17 | PrimaryKeyRelatedField, |
17 | 18 | SelfNestedField, |
33 | 34 | class TestSelfNestedField: |
34 | 35 | |
35 | 36 | def load(self, data, schema=PlaceSchema): |
36 | return schema(strict=True).load(data).data | |
37 | return schema().load(data) | |
37 | 38 | |
38 | 39 | def test_field_serialization(self): |
39 | 40 | point = Place('home', 123, 456.1) |
40 | 41 | schema = PlaceSchema() |
41 | dumped = schema.dump(point).data | |
42 | dumped = schema.dump(point) | |
42 | 43 | assert dumped == {"name": "home", "coords": {"x": 123.0, "y": 456.1}} |
43 | 44 | |
44 | 45 | def test_deserialization_success(self): |
45 | load = PlaceSchema().load({"coords": {"x": 123.0, "y": 456.1}}).data | |
46 | load = PlaceSchema().load({"coords": {"x": 123.0, "y": 456.1}}) | |
46 | 47 | assert load == {"coords": {"x": 123.0, "y": 456.1}} |
47 | 48 | |
48 | 49 | @pytest.mark.parametrize('data', [ |
103 | 104 | self.profile = Profile(self.user, 'david') |
104 | 105 | |
105 | 106 | def serialize(self, obj=None, schema=UserSchema): |
106 | return schema(strict=True).dump(obj or self.user).data | |
107 | return schema().dump(obj or self.user) | |
107 | 108 | |
108 | 109 | def test_many_id(self): |
109 | 110 | assert self.serialize() == {"username": "test", |
154 | 155 | self.blogpost = Blogpost2(1, 'test', self.user) |
155 | 156 | |
156 | 157 | def serialize(self, obj=None, schema=Blogpost2Schema): |
157 | return schema(strict=True).dump(obj or self.blogpost).data | |
158 | return schema().dump(obj or self.blogpost) | |
158 | 159 | |
159 | 160 | def load(self, data, schema=Blogpost2Schema): |
160 | return schema(strict=True).load(data).data | |
161 | return schema().load(data) | |
161 | 162 | |
162 | 163 | def test_serialize(self): |
163 | 164 | assert self.serialize() == self.serialized_data |
185 | 186 | |
186 | 187 | def get_x(self, obj): |
187 | 188 | return 5 |
189 | ||
188 | 190 | assert self.serialize(Place('test', 1, 1), PlaceSchema) == { |
189 | 191 | "name": "test", |
190 | 192 | "x": 5, |
191 | 193 | } |
192 | 194 | |
193 | 195 | |
194 | # I'm Py3 | |
196 | class TestNullToBlankString: | |
197 | ||
198 | class NullToBlankSchema(Schema): | |
199 | string = NullToBlankString(missing='test') | |
200 | ||
201 | def test_load_simple_string(self): | |
202 | data = self.NullToBlankSchema().load({'string': 'hello'}) | |
203 | assert data['string'] == 'hello' | |
204 | ||
205 | def test_load_string_with_null_bytes(self): | |
206 | data = self.NullToBlankSchema().load({'string': 'hello\0world'}) | |
207 | assert data['string'] == 'helloworld' | |
208 | ||
209 | def test_load_default_string(self): | |
210 | data = self.NullToBlankSchema().load({}) | |
211 | assert data['string'] == 'test' | |
212 | ||
213 | def test_translate_none_to_empty_string(self): | |
214 | data = self.NullToBlankSchema().load({'string': None}) | |
215 | assert data['string'] == '' |
871 | 871 | session.commit() |
872 | 872 | rules_data = [] |
873 | 873 | for rule in rules: |
874 | rule_data = WorkerRuleSchema().dumps(rule).data | |
874 | rule_data = WorkerRuleSchema().dumps(rule) | |
875 | 875 | rules_data.append(json.loads(rule_data)) |
876 | 876 | searcher.process(rules_data) |
877 | 877 | vulns_count = session.query(Vulnerability).filter_by(workspace=workspace).count() |