Codebase list python-faraday / 98e41a2
New upstream version 3.11.1 Sophie Brun 3 years ago
50 changed file(s) with 410 addition(s) and 295 deletion(s). Raw diff Collapse all Expand all
306306 stage: build
307307 image: centos:7
308308 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
322322 script:
323323 - mkdir build_installer
324324 - cp -a faraday.tar.gz build_installer/.
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
77 New features in the latest update
88 =====================================
99
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
1016
1117 3.11 [Apr 22nd, 2020]:
1218 ---
77 New features in the latest update
88 =====================================
99
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
1016
1117 3.11 [Apr 22nd, 2020]:
1218 ---
11 # Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/)
22 # See the file 'doc/LICENSE' for the license information
33
4 __version__ = '3.11'
4 __version__ = '3.11.1'
55 __license_version__ = __version__
88
99 import flask
1010 import sqlalchemy
11 import datetime
1112 from collections import defaultdict
1213 from flask import g
1314 from flask_classful import FlaskView
1516 from sqlalchemy.orm.exc import NoResultFound, ObjectDeletedError
1617 from sqlalchemy.inspection import inspect
1718 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
2020 from marshmallow.validate import Length
2121 from marshmallow_sqlalchemy import ModelConverter
2222 from marshmallow_sqlalchemy.schema import ModelSchemaMeta, ModelSchemaOpts
23 from webargs.flaskparser import FlaskParser, parser as parser_imported
23 from webargs.flaskparser import FlaskParser
2424 from webargs.core import ValidationError
2525 from faraday.server.models import Workspace, db, Command, CommandObject
2626 from faraday.server.schemas import NullToBlankString
161161 def _get_schema_instance(self, route_kwargs, **kwargs):
162162 """Instances a model schema.
163163
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
168164 It also uses _set_schema_context to set the context of the
169165 schema.
170166 """
171 if 'strict' not in kwargs:
172 kwargs['strict'] = True
173167 kwargs['context'] = self._set_schema_context(
174168 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
175174 return self._get_schema_class()(**kwargs)
176175
177176 def _set_schema_context(self, context, **kwargs):
284283 TODO migration: document route_kwargs
285284 """
286285 try:
287 return self._get_schema_instance(route_kwargs, **kwargs).dump(obj).data
286 return self._get_schema_instance(route_kwargs, **kwargs).dump(obj)
288287 except ObjectDeletedError:
289288 return []
290289
293292 data. It a ``Marshmallow.Schema`` instance to perform the
294293 deserialization
295294 """
296 return FlaskParser().parse(schema, request, locations=('json',),
295 return FlaskParser().parse(schema, request, location="json",
297296 *args, **kwargs)
298297
299298 @classmethod
713712 {
714713 'message': 'Existing value',
715714 'object': self._get_schema_class()().dump(
716 conflict_obj).data,
715 conflict_obj),
717716 }
718717 ))
719718 else:
822821 {
823822 'message': 'Existing value',
824823 'object': self._get_schema_class()().dump(
825 conflict_obj).data,
824 conflict_obj),
826825 }
827826 ))
828827 else:
906905 {
907906 'message': 'Existing value',
908907 'object': self._get_schema_class()().dump(
909 conflict_obj).data,
908 conflict_obj),
910909 }
911910 ))
912911 else:
12131212 self.model_converter = CustomModelConverter
12141213
12151214
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):
12171251 """
12181252 A Marshmallow schema that does field introspection based on
12191253 the SQLAlchemy model specified in Meta.model.
12261260 TYPE_MAPPING = Schema.TYPE_MAPPING.copy()
12271261 TYPE_MAPPING[str] = NullToBlankString
12281262
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
12291297
12301298 class FilterAlchemyModelConverter(ModelConverter):
12311299 """Use this to make all fields of a model not required.
12381306 kwargs['required'] = False
12391307
12401308
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
12411316 class FilterSetMeta:
12421317 """Base Meta class of FilterSet objects"""
1243 parser = parser_imported
1318 parser = AutoSchemaFlaskParser(location='query')
12441319 converter = FilterAlchemyModelConverter()
12451320
12461321
55
66 from flask import Blueprint, abort, request
77 from flask_classful import route
8 from marshmallow import fields, Schema
8 from marshmallow import fields, Schema, EXCLUDE
99 from sqlalchemy.orm.exc import NoResultFound
1010
1111
108108
109109
110110 class AgentRunSchema(Schema):
111 executorData = fields.Nested(ExecutorDataSchema(), required=True)
111 executorData = fields.Nested(
112 ExecutorDataSchema(unknown=EXCLUDE),
113 required=True
114 )
112115
113116
114117 class AgentView(UpdateWorkspacedMixin,
137140 """
138141 if flask.request.content_type != 'application/json':
139142 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)
141144 agent = self._get_object(agent_id, workspace_name)
142145 executor_data = data['executorData']
143146
2424
2525 def index(self):
2626 return AgentAuthTokenSchema().dump(
27 {'token': faraday_server.agent_token}).data
27 {'token': faraday_server.agent_token})
2828
2929 def post(self):
3030 from faraday.server.app import save_new_agent_creation_token # pylint:disable=import-outside-toplevel
3434 flask.abort(403)
3535 save_new_agent_creation_token()
3636 return AgentAuthTokenSchema().dump(
37 {'token': faraday_server.agent_token}).data
37 {'token': faraday_server.agent_token})
3838
3939
4040 AgentAuthTokenView.register(agent_auth_token_api)
6262 def __init__(self, *args, **kwargs):
6363 super(PolymorphicVulnerabilityField, self).__init__(*args, **kwargs)
6464 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):
6969 if self.many and not utils.is_collection(value):
7070 self.fail('type', input=value, type=value.__class__.__name__)
7171 if self.many:
8383 schema = self.vulnweb_schema
8484 else:
8585 raise ValidationError('type must be "Vulnerability" or "VulnerabilityWeb"')
86 return schema.load(value).data
86 return schema.load(value)
8787
8888
8989 class BulkCredentialSchema(AutoSchema):
9696 """It's like the original service schema, but now it only uses port
9797 instead of ports (a single integer array). That field was only used
9898 to keep backwards compatibility with the Web UI"""
99 port = fields.Integer(strict=True, required=True,
99 port = fields.Integer(required=True,
100100 validate=[Range(min=0, error="The value must be greater than or equal to 0")])
101101 vulnerabilities = PolymorphicVulnerabilityField(
102 VulnerabilitySchema(many=True),
102 # VulnerabilitySchema(many=True), # I have no idea what this line does, but breaks with marshmallow 3
103103 many=True,
104104 missing=[],
105105 )
158158 )
159159
160160 @post_load
161 def load_end_date(self, data):
161 def load_end_date(self, data, **kwargs):
162162 duration = data.pop('duration')
163163 data['end_date'] = data['start_date'] + duration
164 return data
164165
165166
166167 class BulkCreateSchema(Schema):
203204
204205 def bulk_create(ws, data, data_already_deserialized=False):
205206 if not data_already_deserialized:
206 schema = BulkCreateSchema(strict=True)
207 data = schema.load(data).data
207 schema = BulkCreateSchema()
208 data = schema.load(data)
208209 if 'command' in data:
209210 command = _create_command(ws, data['command'])
210211 else:
88
99 from faraday.server.api.base import AutoSchema, ReadWriteWorkspacedView, PaginatedMixin
1010 from faraday.server.models import Command, Workspace
11 from faraday.server.schemas import PrimaryKeyRelatedField
11 from faraday.server.schemas import MutableField, PrimaryKeyRelatedField
1212
1313 commandsrun_api = Blueprint('commandsrun_api', __name__)
1414
1616 class CommandSchema(AutoSchema):
1717 _id = fields.Integer(dump_only=True, attribute='id')
1818 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)
2023 workspace = PrimaryKeyRelatedField('name', dump_only=True)
2124 creator = PrimaryKeyRelatedField('username', dump_only=True)
2225
3942 return 'In progress'
4043
4144 @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):
4346 # there is a potential bug when updating, the start_date can be changed.
4447 duration = data.pop('duration', None)
4548 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
4752
4853 class Meta:
4954 model = Command
7171 abort(409, ValidationError(
7272 {
7373 'message': 'Comment already exists',
74 'object': self.schema_class().dump(comment).data,
74 'object': self.schema_class().dump(comment),
7575 }
7676 ))
7777 res = super(UniqueCommentView, self)._perform_create(data, workspace_name)
6666 )
6767
6868 @post_load
69 def set_parent(self, data):
69 def set_parent(self, data, **kwargs):
7070 parent_type = data.pop('parent_type', None)
7171 parent_id = data.pop('parent', None)
7272 if parent_type == 'Host':
208208 @route('/<host_id>/services/')
209209 def service_list(self, workspace_name, host_id):
210210 services = self._get_object(host_id, workspace_name).services
211 return ServiceSchema(many=True).dump(services).data
211 return ServiceSchema(many=True).dump(services)
212212
213213 @route('/countVulns/')
214214 def count_vulns(self, workspace_name):
236236 host_count = Host.query_with_count(None, host_id_list, workspace_name)
237237
238238 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)
240240 # return counts.data
241241
242242 return res_dict
3434 required=True,
3535 attribute='port')
3636 status = fields.String(missing='open', validate=OneOf(Service.STATUSES),
37 required=True, allow_none=False)
37 allow_none=False)
3838 parent = fields.Integer(attribute='host_id') # parent is not required for updates
3939 host_id = fields.Integer(attribute='host_id', dump_only=True)
4040 vulns = fields.Integer(attribute='vulnerability_count', dump_only=True)
6161 return str(port)
6262
6363 @post_load
64 def post_load_parent(self, data):
64 def post_load_parent(self, data, **kwargs):
6565 """Gets the host_id from parent attribute. Pops it and tries to
6666 get a Host with that id in the corresponding workspace.
6767 """
6969 if self.context['updating']:
7070 if host_id is None:
7171 # Partial update?
72 return
72 return data
7373
7474 if host_id != self.context['object'].parent.id:
7575 raise ValidationError('Can\'t change service parent.')
8585 ).one()
8686 except NoResultFound:
8787 raise ValidationError('Host with id {} not found'.format(host_id))
88
89 return data
8890
8991 class Meta:
9092 model = Service
1111
1212 @session_api.route('/session')
1313 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
1515 data = user.get_security_payload()
1616 data['csrf_token'] = generate_csrf()
1717 data['preferences'] = user.preferences
106106 return references
107107
108108 @post_load
109 def post_load_impact(self, data):
109 def post_load_impact(self, data, **kwargs):
110110 # Unflatten impact (move data[impact][*] to data[*])
111111 impact = data.pop('impact', None)
112112 if impact:
2020 from sqlalchemy.orm import aliased, joinedload, selectin_polymorphic, undefer
2121 from sqlalchemy.orm.exc import NoResultFound
2222 from sqlalchemy import desc, or_
23 from werkzeug.datastructures import ImmutableMultiDict
2324
2425 from depot.manager import DepotManager
2526 from faraday.server.api.base import (
170171
171172 for file_obj in obj.evidence:
172173 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)
177175 except IOError:
178176 logger.warning("File not found. Did you move your server?")
179177
220218 return value
221219
222220 @post_load
223 def post_load_impact(self, data):
221 def post_load_impact(self, data, **kwargs):
224222 # Unflatten impact (move data[impact][*] to data[*])
225223 impact = data.pop('impact', None)
226224 if impact:
228226 return data
229227
230228 @post_load
231 def post_load_parent(self, data):
229 def post_load_parent(self, data, **kwargs):
232230 # schema guarantees that parent_type exists.
233231 parent_class = None
234232 parent_type = data.pop('parent_type', None)
235233 parent_id = data.pop('parent', None)
236234 if not (parent_type and parent_id):
237235 # Probably a partial load, since they are required
238 return
236 return data
239237 if parent_type == 'Host':
240238 parent_class = Host
241239 parent_field = 'host_id'
369367 # TODO migration: Check if we should add fields owner,
370368 # command, impact, issuetracker, tags, date, host
371369 # evidence, policy violations, hostnames
370
372371 fields = (
373 "id", "status", "website", "pname", "query", "path", "service",
372 "id", "status", "website", "parameter_name", "query_string", "path", "service",
374373 "data", "severity", "confirmed", "name", "request", "response",
375 "parameters", "params", "resolution", "ease_of_resolution",
374 "parameters", "resolution",
376375 "description", "command_id", "target", "creator", "method",
377 "easeofresolution", "query_string", "parameter_name", "service_id",
376 "ease_of_resolution", "service_id",
378377 "status_code", "tool",
379378 )
380379
381380 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",
384383 )
385384
386385 default_operator = CustomILike
396395 creator = CreatorFilter(fields.Str())
397396 service = ServiceFilter(fields.Str())
398397 severity = Filter(SeverityField())
399 easeofresolution = Filter(fields.String(
400 attribute='ease_of_resolution',
398 ease_of_resolution = Filter(fields.String(
401399 validate=OneOf(Vulnerability.EASE_OF_RESOLUTIONS),
402400 allow_none=True))
403 pname = Filter(fields.String(attribute='parameter_name'))
404 query = Filter(fields.String(attribute='query_string'))
405401 status_code = StatusCodeFilter(fields.Int())
406 params = Filter(fields.String(attribute='parameters'))
407402 status = Filter(fields.Function(
408403 deserialize=lambda val: 'open' if val == 'opened' else val,
409404 validate=OneOf(Vulnerability.STATUSES + ['opened'])
419414 # TODO migration: this can became a normal filter instead of a custom
420415 # one, since now we can use creator_command_id
421416 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
422434 query = super(VulnerabilityFilterSet, self).filter()
423435
424436 if command_id:
725737
726738 normal_vulns = self.schema_class_dict['VulnerabilityWeb'](**marshmallow_params).dumps(normal_vulns.all(),
727739 cls=BytesJSONEncoder)
728 normal_vulns_data = json.loads(normal_vulns.data)
740 normal_vulns_data = json.loads(normal_vulns)
729741 except Exception as ex:
730742 logger.exception(ex)
731743 normal_vulns_data = []
742754 web_vulns = web_vulns.join(Service).join(Host).join(Hostname).filter(or_(*or_filters))
743755 web_vulns = self.schema_class_dict['VulnerabilityWeb'](**marshmallow_params).dumps(web_vulns.all(),
744756 cls=BytesJSONEncoder)
745 web_vulns_data = json.loads(web_vulns.data)
757 web_vulns_data = json.loads(web_vulns)
746758 except Exception as ex:
747759 logger.exception(ex)
748760 web_vulns_data = []
805817 object_id=vuln_id).all()
806818 res = {}
807819 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)
811821 res[file_obj.filename] = ret
812822
813823 return flask.jsonify(res)
5353 class WorkspaceSchema(AutoSchema):
5454
5555 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"))
5757 stats = SelfNestedField(WorkspaceSummarySchema())
5858 duration = SelfNestedField(WorkspaceDurationSchema())
5959 _id = fields.Integer(dump_only=True, attribute='id')
7777 'create_date', 'update_date', 'readonly')
7878
7979 @post_load
80 def post_load_duration(self, data):
80 def post_load_duration(self, data, **kwargs):
8181 # Unflatten duration (move data[duration][*] to data[*])
8282 duration = data.pop('duration', None)
8383 if duration:
222222
223223
224224 WorkspaceView.register(workspace_api)
225 # I'm Py3⏎
225 # I'm Py3
168168 logger.warn('Invalid authentication token.')
169169 flask.abort(401)
170170 logged_in = True
171 flask.session['user_id'] = user.id
171 flask.session['_user_id'] = user.id # TODO use public flask_login functions
172172 elif auth_type == 'agent':
173173 # Don't handle the agent logic here, do it in another
174174 # before_request handler
177177 logger.warn("Invalid authorization type")
178178 flask.abort(401)
179179 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")
182183 if logged_in:
183184 user = User.query.filter_by(id=user_id).first()
184185
192193 if logged_in:
193194 g.user = user
194195 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']
197198 flask.abort(401) # 403 would be better but breaks the web ui
198199 return
199200
77 import time
88 import datetime
99 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_
1112 from marshmallow.exceptions import ValidationError
12 from marshmallow.utils import missing
1313 from dateutil.tz import tzutc
1414
1515 from faraday.server.models import (
2727 if value is not None:
2828 return int(time.mktime(value.timetuple()) * 1000)
2929
30 def _deserialize(self, value, attr, data):
30 def _deserialize(self, value, attr, data, **kwargs):
3131 if value is not None and value:
3232 return datetime.datetime.fromtimestamp(self._validated(value)/1e3)
3333
112112 return None
113113 return getattr(value, self.field_name)
114114
115 def _deserialize(self, value, attr, data):
115 def _deserialize(self, value, attr, data, **kwargs):
116116 raise NotImplementedError("Only dump is implemented for now")
117117
118118
129129 super(SelfNestedField, self).__init__(*args, **kwargs)
130130
131131 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):
138135 """
139136 It would be awesome if this method could also flatten the dict keys into the parent
140137 """
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)
146139
147140
148141 class MutableField(fields.Field):
171164 super(MutableField, self).__init__(**kwargs)
172165
173166 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
174171 return self.read_field._serialize(value, attr, obj)
175172
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)
178179
179180 def _add_to_schema(self, field_name, schema):
180181 # Propagate to child fields
197198 return 'info'
198199 return ret
199200
200 def _deserialize(self, value, attr, data):
201 def _deserialize(self, value, attr, data, **kwargs):
201202 ret = super(SeverityField, self)._serialize(value, attr, data)
202203 if ret == 'med':
203204 return 'medium'
222223 self.allow_none = True
223224 self.default = ''
224225
225 def deserialize(self, value, attr=None, data=None):
226 def deserialize(self, value, attr=None, data=None, **kwargs):
226227 # Validate required fields, deserialize, then validate
227228 # deserialized value
229 self._validate_missing(value)
230 if value is missing_:
231 _miss = self.missing
232 return _miss() if callable(_miss) else _miss
228233 if isinstance(value, str):
229234 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:
231236 raise ValidationError("Deserializing a non string field when expected")
232 self._validate_missing(value)
233237 if getattr(self, 'allow_none', False) is True and value is None:
234238 return ''
235 output = self._deserialize(value, attr, data)
239 output = self._deserialize(value, attr, data, **kwargs)
236240 self._validate(output)
237241 return output
238242
250254 update_action = fields.Integer(default=0, dump_only=True)
251255 update_controller_action = fields.String(default='', dump_only=True)
252256
257 class Meta:
258 unknown = EXCLUDE
259
253260
254261 class StrictDateTimeField(fields.DateTime):
255262 """
265272 super(StrictDateTimeField, self).__init__(*args, **kwargs)
266273 self.load_as_tz_aware = load_as_tz_aware
267274
268 def _deserialize(self, value, attr, data):
275 def _deserialize(self, value, attr, data, **kwargs):
269276 if isinstance(value, datetime.datetime):
270277 date = value
271278 else:
333340 return '{}={}'.format(object_rule_name, value)
334341
335342 @post_dump
336 def remove_none_values(self, data):
343 def remove_none_values(self, data, **kwargs):
337344 actions = []
338345 conditions = []
339346 for action in data['actions']:
349356 if value
350357 }
351358
352 # I'm Py3⏎
359 # I'm Py3
3131 from faraday.server.web import app # pylint:disable=import-outside-toplevel
3232 with app.app_context():
3333 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)
3636 data = add_creator(data, user)
3737 bulk_create(ws, data, True)
3838
7474 self.stop()
7575 except Exception as ex:
7676 logger.exception(ex)
77 continue⏎
77 continue
+0
-21
faraday/server/utils/cache.py less more
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⏎
8484 <br><br><br><br>
8585 <p>This feature belongs to our commercial versions.
8686 <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>
8888 </div><!-- .jumbotron -->
8989 </div><!-- #reports-main --></div><!-- .right-main -->
9090 </section><!-- #main -->
55 <h4 class="modal-title"><img src="/images/faraday-iso.svg" height="30" style="display: inline; padding: 5px; vertical-align:middle;"> About Faraday</h4>
66 </div>
77 <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>
99 <br/>
1010 <h5>The collaborative penetration test environment the world needed!</h5>
1111 </div><!-- .modal-body -->
1919 <tr ng-repeat="host in hosts | limitTo:5">
2020 <td class="">
2121 <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>
2325 </td>
2426 <td class="">{{host.services}}</td>
2527 <td class="">
183183 <td class="ui-grid-cell-contents"
184184 ng-show="columns['search_in_shodan']">
185185 <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>
187189 </div>
188190 </td>
189191 <td class="ui-grid-cell-contents"
55
66 nixpkgs = builtins.fetchTarball {
77 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";
1010 };
1111
1212 packageOverrides = self: super: {
1313
1414 faradaysec = self.callPackage ./packages/faradaysec.nix { };
1515
16 flask = self.callPackage ./packages/flask.nix { };
16 werkzeug = self.callPackage ./packages/werkzeug.nix { };
1717
1818 flask-classful = self.callPackage ./packages/flask-classful.nix { };
1919
20 flask-login = self.callPackage ./packages/flask-login.nix { };
21
2022 flask-security = self.callPackage ./packages/flask-security.nix { };
2123
22 flask-babelex = self.callPackage ./packages/flask-babelex.nix { };
23
24 pgcli = self.callPackage ./packages/pgcli.nix { };
25
2624 webargs = self.callPackage ./packages/webargs.nix { };
27
28 marshmallow-sqlalchemy =
29 self.callPackage ./packages/marshmallow-sqlalchemy.nix { };
3025
3126 filteralchemy-fork = self.callPackage ./packages/filteralchemy-fork.nix { };
3227
00 { 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
44 , marshmallow, marshmallow-sqlalchemy, nplusone, pgcli, pillow, psycopg2, pyasn1
55 , 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 }:
78 buildPythonPackage rec {
89 pname = "faradaysec";
910 version = "0.1dev";
2425 flask
2526 flask_sqlalchemy
2627 flask-classful
27 flask_login
28 email_validator
29 wtforms
30 flask-login
2831 flask-security
2932 marshmallow
3033 pillow
+0
-17
pypi2nixpkgs/packages/flask-babelex.nix less more
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
11 , flask_mail, flask_principal, flask_wtf, itsdangerous, lib, passlib
22 , pytestrunner }:
33 buildPythonPackage rec {
1616 buildInputs = [ Babel pytestrunner ];
1717 propagatedBuildInputs = [
1818 flask
19 flask_login
19 flask-login
2020 flask_mail
2121 flask_principal
2222 flask_wtf
+0
-17
pypi2nixpkgs/packages/flask.nix less more
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
-17
pypi2nixpkgs/packages/marshmallow-sqlalchemy.nix less more
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
-29
pypi2nixpkgs/packages/pgcli.nix less more
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 }
00 { buildPythonPackage, fetchPypi, lib }:
11 buildPythonPackage rec {
22 pname = "simplekv";
3 version = "0.13.0";
3 version = "0.14.1";
44
55 src = builtins.fetchurl {
66 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";
99 };
1010
1111 # TODO FIXME
00 { buildPythonPackage, fetchPypi, lib, marshmallow }:
11 buildPythonPackage rec {
22 pname = "webargs";
3 version = "5.5.3";
3 version = "6.1.0";
44
55 src = builtins.fetchurl {
66 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";
99 };
1010
1111 # 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
11 autobahn>=17.10.1
22 alembic>=0.9.9
33 bcrypt>=3.1.4
66 flask>=1.1
77 Flask-SQLAlchemy>=2.3.1
88 flask-classful>=0.14
9 flask-login<0.5.0
9 email_validator
10 WTForms>=2.1
11 flask-login>=0.5.0
1012 Flask-Security>=3.0.0
11 marshmallow<3.0.0
13 marshmallow>=3.0.0
1214 Pillow>=4.2.1
13 psycopg2==2.7.7
14 pgcli==2.1.0
15 psycopg2
16 pgcli
1517 pyopenssl>=17.2.0
1618 python-dateutil>=2.6.0
1719 requests>=2.18.4
2022 SQLAlchemy>=1.2.0b2
2123 tqdm>=4.15.0
2224 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
2527 filteralchemy-fork
2628 filedepot>=0.5.0
2729 nplusone>=0.8.1
28 Flask-Restless==0.17.0
30 Flask-Restless>=0.17.0
2931 simplejson>=3.16.0
3032 syslog-rfc5424-formatter>=1.1.1
31 simplekv==0.13.0
33 simplekv>=0.13.0
3234 Flask-KVSession-fork>=0.6.3
3335 distro>=1.4.0
3436 faraday-plugins>=1.0.1,<2.0.0
270270 # http://pythonhosted.org/Flask-Testing/#testing-with-sqlalchemy
271271 assert user.id is not None
272272 db.session.add(user)
273 sess['user_id'] = user.id
273 sess['_user_id'] = user.id # TODO use public flask_login functions
274274 identity_changed.send(test_client.application,
275275 identity=Identity(user.id))
276276
5757 .format(ws_name=ws.name, id=command.id),
5858 data=data,
5959 )
60 assert res.status_code == 200
6061
6162 # Changing res.json['itime'] to timestamp format of itime
6263 res_itime = res.json['itime'] / 1000.0
6364 assert res.status_code == 200
6465 assert res_itime == itime
65
66
67 # I'm Py3
3737 'availability': False,
3838 },
3939 'refs': ['CVE-1234'],
40 'tool': 'some_tool'
40 'tool': 'some_tool',
41 'data': 'test data',
4142 }
4243
4344 vuln_web_data = {
113114
114115
115116 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)
117118 bc._create_service(host.workspace, host, data)
118119 assert count(Service, host.workspace) == 1
119120 service = Service.query.filter(Service.workspace == host.workspace).one()
129130 "port": service.port,
130131 "protocol": service.protocol,
131132 }
132 data = bc.BulkServiceSchema(strict=True).load(data).data
133 data = bc.BulkServiceSchema(strict=True).load(data)
133134 bc._create_service(service.workspace, service.host, data)
134135 assert count(Service, service.host.workspace) == 1
135136
136137 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)
138139 bc._create_hostvuln(host.workspace, host, data)
139140 assert count(VulnerabilityGeneric, host.workspace) == 1
140141 assert count(Vulnerability, host.workspace) == 1
150151
151152
152153 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)
154155 bc._create_servicevuln(service.workspace, service, data)
155156 assert count(VulnerabilityGeneric, service.workspace) == 1
156157 assert count(Vulnerability, service.workspace) == 1
169170 def test_create_host_vuln_without_tool(session, host):
170171 no_tool_data = vuln_data.copy()
171172 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)
173174 bc._create_hostvuln(host.workspace, host, data)
174175 vuln = host.workspace.vulnerabilities[0]
175176 assert vuln.tool == "Web UI"
222223 'type': 'Vulnerability',
223224 'refs': ['new']
224225 }
225 data = bc.VulnerabilitySchema(strict=True).load(data).data
226 data = bc.VulnerabilitySchema(strict=True).load(data)
226227 bc._create_hostvuln(host.workspace, host, data)
227228 session.commit()
228229 assert count(Vulnerability, host.workspace) == 1
276277 def test_create_service_with_vuln(session, host):
277278 service_data_ = service_data.copy()
278279 service_data_['vulnerabilities'] = [vuln_data]
279 data = bc.BulkServiceSchema(strict=True).load(service_data_).data
280 data = bc.BulkServiceSchema(strict=True).load(service_data_)
280281 bc._create_service(host.workspace, host, data)
281282 assert count(Service, host.workspace) == 1
282283 service = host.workspace.services[0]
290291 def test_create_service_with_cred(session, host):
291292 service_data_ = service_data.copy()
292293 service_data_['credentials'] = [credential_data]
293 data = bc.BulkServiceSchema(strict=True).load(service_data_).data
294 data = bc.BulkServiceSchema(strict=True).load(service_data_)
294295 bc._create_service(host.workspace, host, data)
295296 assert count(Service, host.workspace) == 1
296297 service = host.workspace.services[0]
309310 del vuln_data_['name']
310311 service_data_['vulnerabilities'] = [vuln_data_]
311312 with pytest.raises(ValidationError):
312 data = bc.BulkServiceSchema(strict=True).load(service_data_).data
313 data = bc.BulkServiceSchema(strict=True).load(service_data_)
313314 bc._create_service(host.workspace, host, data)
314315 assert count(Service, host.workspace) == 0
315316 assert count(Vulnerability, host.workspace) == 0
321322 del vuln_data_['name']
322323 service_data_['vulnerabilities'] = [1, 2, 3]
323324 with pytest.raises(ValidationError):
324 data = bc.BulkServiceSchema(strict=True).load(service_data_).data
325 data = bc.BulkServiceSchema(strict=True).load(service_data_)
325326 bc._create_service(host.workspace, host, data)
326327 assert count(Service, host.workspace) == 0
327328 assert count(Vulnerability, host.workspace) == 0
332333 vuln_data_ = vuln_data.copy()
333334 vuln_data_.update(vuln_web_data)
334335 service_data_['vulnerabilities'] = [vuln_data_]
335 data = bc.BulkServiceSchema(strict=True).load(service_data_).data
336 data = bc.BulkServiceSchema(strict=True).load(service_data_)
336337 bc._create_service(host.workspace, host, data)
337338 assert count(Service, host.workspace) == 1
338339 service = host.workspace.services[0]
372372 def test_update_command(self, test_client, session):
373373 command = self.factory()
374374 session.commit()
375 raw_data ={
375 raw_data = {
376376 'command': 'Import Nessus:',
377377 'tool': 'nessus',
378378 'duration': 120,
383383 'user': 'lcubo'
384384 }
385385
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)
387388 assert res.status_code == 200
388389 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)
390393
391394 def test_delete_objects_preserve_history(self, session, test_client):
392395
5656 raw_comment = self._create_raw_comment('workspace', service.id)
5757 res = test_client.post(self.url(), data=raw_comment)
5858 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]
6060
6161 def test_cannot_create_comment_of_another_workspace_object(self, test_client, session, second_workspace):
6262 service = ServiceFactory.create(workspace=self.workspace)
166166 }
167167 res = test_client.post(self.url(), data=raw_data)
168168 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']
170170
171171
172172 def test_update_credentials(self, test_client, session, host):
8787 }
8888 res = test_client.post(self.url(), data=data)
8989 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
9191
9292 def test_create_fails_with_no_host_id(self, test_client,
9393 host, session):
207207 session.commit()
208208 raw_data = self._raw_put_data(service.id, parent=service.host.id, status='closed')
209209 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
211211 updated_service = Service.query.filter_by(id=service.id).first()
212212 assert updated_service.status == 'closed'
213213
216216 session.commit()
217217 raw_data = self._raw_put_data(service.id, parent=service.host.id, ports=[221])
218218 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
220220 updated_service = Service.query.filter_by(id=service.id).first()
221221 assert updated_service.port == 221
222222
226226 session.commit()
227227 raw_data = self._raw_put_data(service.id)
228228 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
230230 assert res.json['id'] == service.id
231231
232232 def test_create_service_from_command(self, test_client, session):
265265 }
266266 res = test_client.post(self.url(), data=data)
267267 assert res.status_code == 400
268 assert res.json['messages']['_schema'] == res.json['messages']['_schema']
269268
270269 def test_load_ports_without_list(self, test_client):
271270 data = {
660660 assert res.json['method'] == 'GET'
661661 assert res.json['path'] == '/pepep'
662662
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
663696 @pytest.mark.usefixtures('mock_envelope_list')
664697 @pytest.mark.parametrize('medium_name', ['medium', 'med'])
665698 def test_filter_by_severity(self, test_client, session,
10221055 res = test_client.post('/v2/ws/{0}/vulns/'.format(ws_name), data=raw_data)
10231056 assert res.status_code == 400
10241057 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]
10271060
10281061 def test_create_vuln_with_null_ease_of_resolution(self,
10291062 host_with_hostnames,
15521585 'Host',
15531586 )
15541587 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
15561589
15571590 def test_create_and_update_webvuln(self, host_with_hostnames, test_client, session):
15581591 """
16861719
16871720 res = test_client.post(self.url(), data=raw_data)
16881721 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']}}}
16901723
16911724 def test_after_deleting_vuln_ref_and_policies_remains(self, session, test_client):
16921725 vuln = VulnerabilityFactory.create(workspace=self.workspace)
17921825 'parent_type': 'Host',
17931826 'parent': host.id,
17941827 'type': 'Vulnerability',
1795 'refs': ''
17961828 }
17971829 res = test_client.post(self.url(), data=data)
17981830 assert res.status_code == 201
20172049 policyviolations=[],
20182050 )
20192051 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'
20212053
20222054 def test_add_empty_attachment(self, test_client, session, workspace, csrf_token):
20232055 vuln = VulnerabilityFactory.create(workspace=workspace)
233233 workspace=self.first_object.workspace))
234234
235235 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()
237239
238240 creators = []
239241 grouped = 0
251253 workspace=self.first_object.workspace))
252254
253255 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()
255259
256260 creators = []
257261 grouped = 0
1212 from marshmallow import Schema, fields, ValidationError
1313 from faraday.server.schemas import (
1414 JSTimestampField,
15 NullToBlankString,
1516 MutableField,
1617 PrimaryKeyRelatedField,
1718 SelfNestedField,
3334 class TestSelfNestedField:
3435
3536 def load(self, data, schema=PlaceSchema):
36 return schema(strict=True).load(data).data
37 return schema().load(data)
3738
3839 def test_field_serialization(self):
3940 point = Place('home', 123, 456.1)
4041 schema = PlaceSchema()
41 dumped = schema.dump(point).data
42 dumped = schema.dump(point)
4243 assert dumped == {"name": "home", "coords": {"x": 123.0, "y": 456.1}}
4344
4445 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}})
4647 assert load == {"coords": {"x": 123.0, "y": 456.1}}
4748
4849 @pytest.mark.parametrize('data', [
103104 self.profile = Profile(self.user, 'david')
104105
105106 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)
107108
108109 def test_many_id(self):
109110 assert self.serialize() == {"username": "test",
154155 self.blogpost = Blogpost2(1, 'test', self.user)
155156
156157 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)
158159
159160 def load(self, data, schema=Blogpost2Schema):
160 return schema(strict=True).load(data).data
161 return schema().load(data)
161162
162163 def test_serialize(self):
163164 assert self.serialize() == self.serialized_data
185186
186187 def get_x(self, obj):
187188 return 5
189
188190 assert self.serialize(Place('test', 1, 1), PlaceSchema) == {
189191 "name": "test",
190192 "x": 5,
191193 }
192194
193195
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'] == ''
871871 session.commit()
872872 rules_data = []
873873 for rule in rules:
874 rule_data = WorkerRuleSchema().dumps(rule).data
874 rule_data = WorkerRuleSchema().dumps(rule)
875875 rules_data.append(json.loads(rule_data))
876876 searcher.process(rules_data)
877877 vulns_count = session.query(Vulnerability).filter_by(workspace=workspace).count()