Codebase list python-faraday / 78dcaf8
Update upstream source from tag 'upstream/3.19.0' Update to upstream version '3.19.0' with Debian dir f1ba2617a83d5ca16cc870128ce4d88455e969e4 Sophie Brun 2 years ago
120 changed file(s) with 3475 addition(s) and 991 deletion(s). Raw diff Collapse all Expand all
0 # When should run
1 .dev-staging-master:
2 rules:
3 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master|staging|dev)$/'
4 when: on_success
5
6 .on-master-staging:
7 rules:
8 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master|staging)$/'
9 when: on_success
10
11 .on-master:
12 rules:
13 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master)$/'
14 when: on_success
15
16 .on-community-tag:
17 rules:
18 - if: '$CI_COMMIT_TAG =~ /^white-v[0-9.]+$/'
19 when: on_success
20
21 .pipeline-control-test:
22 rules:
23 - if: $FULL_TEST || $DAILY_TEST
24 when: on_success
25
26 .be-built:
27 rules:
28 - if: '$CI_COMMIT_TAG || $BUILD_TEST || $FULL_TEST || $DAILY_TEST'
29 when: on_success
30
31 .be-uploaded:
32 rules:
33 - if: '$BUILD_TEST || $FULL_TEST || $DAILY_TEST'
34 when: on_success
35
36
37 # Ignore
38 .ignore-on-tag:
39 rules:
40 - if: '$CI_COMMIT_TAG'
41 when: never
42
43 .ignore-on-master:
44 rules:
45 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master)$/'
46 when: never
47
48 .ignore-on-build:
49 rules:
50 - if: $BUILD_TEST
51 when: never
3636 - "faraday-server_amd64.deb"
3737 expire_in: 15 days
3838 rules:
39 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master)$/'
40 when: on_success
41 - if: '$CI_COMMIT_TAG || $BUILD_TEST || $FULL_TEST || $DAILY_TEST'
42 when: on_success
39 - !reference [.on-master-staging, rules]
40 - !reference [.be-built, rules]
4341 - when: never
4442
4543
9088 - "faraday-server_amd64.rpm"
9189 expire_in: 15 days
9290 rules:
93 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master)$/'
94 when: on_success
95 - if: '$CI_COMMIT_TAG || $BUILD_TEST || $FULL_TEST || $DAILY_TEST'
96 when: on_success
91 - !reference [.on-master-staging, rules]
92 - !reference [.be-built, rules]
9793 - when: never
9894
9995 generate_docker_tar_gz:
114110 paths:
115111 - faraday-server-docker.tar.gz
116112 rules:
117 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master)$/'
118 when: on_success
119 - if: '$CI_COMMIT_TAG || $BUILD_TEST || $FULL_TEST || $DAILY_TEST'
120 when: on_success
113 - !reference [.on-master-staging, rules]
114 - !reference [.be-built, rules]
115 - when: never
1919 - "/usr/bin/rsync -aq --exclude 'faraday_copy' --exclude '.cache' . faraday_copy"
2020 - "/bin/tar -zcf faraday.tar.gz faraday_copy"
2121 rules:
22 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master)$/'
23 when: on_success
24 - if: '$CI_COMMIT_TAG || $BUILD_TEST || $FULL_TEST || $DAILY_TEST'
25 when: on_success
22 - !reference [.on-master-staging, rules]
23 - !reference [.be-built, rules]
2624 - when: never
2725 artifacts:
2826 name: 'faraday'
5452 - py3.tar
5553 expire_in: 15 days # in the future we don't need to expire this.
5654 rules:
57 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master)$/'
58 when: on_success
59 - if: '$CI_COMMIT_TAG || $BUILD_TEST || $FULL_TEST || $DAILY_TEST'
60 when: on_success
61 - when: never
55 - !reference [.on-master-staging, rules]
56 - !reference [.be-built, rules]
57 - when: never
2222 - kill $(cat ~faraday/.faraday/faraday-server-port-5985.pid)
2323 - jobs
2424 rules:
25 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master)$/'
26 when: on_success
27 - if: '$CI_COMMIT_TAG || $BUILD_TEST || $FULL_TEST || $DAILY_TEST'
28 when: on_success
25 - !reference [.on-master-staging, rules]
26 - !reference [.be-built, rules]
2927 - when: never
1313 - docker image tag $CI_REGISTRY_IMAGE:latest $CI_REGISTRY_IMAGE:$VERSION
1414 - docker push "$CI_REGISTRY_IMAGE"
1515 rules:
16 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master)$/'
17 when: on_success
16 - !reference [ .on-master, rules ]
1817 needs: # dev won't wait for any previous stage, it will deploy instantly, and
1918 # then run tests in docker image (To be done)
2019 - job: generate_docker_tar_gz
3433 - docker image tag $CI_REGISTRY_IMAGE:latest $CI_REGISTRY_IMAGE:$VERSION
3534 - docker push $CI_REGISTRY_IMAGE:$VERSION
3635 rules:
37 - if: '$CI_COMMIT_TAG =~ /^white-v[0-9.]+$/'
38 when: on_success
36 - !reference [ .on-community-tag, rules ]
3937 dependencies: # prod will wait for any previous stage
4038 - generate_docker_tar_gz
0 update_github:
1 image: python:3
2 stage: publish
3 script:
4 - git remote set-url github https://${GH_USER}:${GH_TOKEN}@github.com/infobyte/faraday.git
5 - git push github $CI_COMMIT_REF_NAME:$DESTINY_BRANCH
6 rules:
7 - if: '$CI_COMMIT_REF_NAME == "white/master"'
8 variables:
9 DESTINY_BRANCH: master
10 when: on_success
11 - if: '$CI_COMMIT_REF_NAME == "white/staging"'
12 variables:
13 DESTINY_BRANCH: staging
14 when: on_success
15 tags:
16 - faradaytests
17
18
19 tag_on_github:
20 image: python:3
21 stage: publish
22 script:
23 - git remote set-url github https://${GH_USER}:${GH_TOKEN}@github.com/infobyte/faraday.git
24 - export FARADAY_VERSION=$(eval $IMAGE_TAG)
25 - CHANGELOG/check_pre_tag.py
26 - git push github $CI_COMMIT_TAG:master
27 - git tag v$FARADAY_VERSION -m "$(cat CHANGELOG/$FARADAY_VERSION/community.md)"
28 - git push github v$FARADAY_VERSION
29 - scripts/github_release.py --deb-file ./faraday-server_amd64.deb --rpm-file ./faraday-server_amd64.rpm
30 rules:
31 - !reference [ .on-community-tag, rules ]
32 dependencies:
33 - generate_deb
34 - generate_rpm
35 tags:
36 - faradaytests
0 tag_on_github:
1 image: python:3
2 stage: publish
3 before_script:
4 script:
5 - git remote set-url github https://${GH_USER}:${GH_TOKEN}@github.com/infobyte/faraday.git
6 - export FARADAY_VERSION=$(eval $IMAGE_TAG)
7 - CHANGELOG/check_pre_tag.py
8 - git push github $CI_COMMIT_TAG:master
9 - git tag v$FARADAY_VERSION -m "$(cat CHANGELOG/$FARADAY_VERSION/white.md)"
10 - git push github v$FARADAY_VERSION
11 - scripts/github_release.py --deb-file ./faraday-server_amd64.deb --rpm-file ./faraday-server_amd64.rpm
12 rules:
13 - if: '$CI_COMMIT_TAG =~ /^white-v[0-9.]+$/'
14 when: on_success
15 dependencies:
16 - generate_deb
17 - generate_rpm
18
190 publish_pypi:
201 image: python:3
212 stage: publish
267 - python setup.py sdist bdist_wheel
278 - twine upload -u $PYPI_USER -p $PYPI_PASS dist/* --verbose
289 rules:
29 - if: '$CI_COMMIT_TAG =~ /^white-v[0-9.]+$/'
30 when: on_success
10 - !reference [ .on-community-tag, rules ]
1919 - mkdir run_from
2020 - nix-shell --command "pytest tests -v --cov=faraday/server/api --disable-warnings --connection-string=postgresql+psycopg2://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres/$POSTGRES_DB -m hypothesis"
2121 rules:
22 - if: '$HYPO_TEST || $FULL_TEST || $DAILY_TEST'
22 - if: $HYPO_TEST
2323 when: on_success
24 - !reference [.pipeline-control-test, rules]
2425 - when: never
1919 - pylint.svg
2020 - pylint3.svg
2121 rules:
22 - if: $BUILD_TEST
23 when: never
24 - if: '$CI_COMMIT_TAG'
25 when: never
26 - if: '$FULL_TEST || $DAILY_TEST'
27 when: on_success
22 - !reference [.ignore-on-build, rules]
23 - !reference [.ignore-on-tag, rules]
24 - !reference [.ignore-on-master, rules]
25 - !reference [.pipeline-control-test, rules]
2826 - when: on_success
2927
3028 .postgresql_test_nix_base:
5654 artifacts: false
5755 # Speed up tests
5856 rules:
59 - if: $BUILD_TEST
60 when: never
61 - if: '$FULL_TEST || $DAILY_TEST'
62 when: on_success
63 - if: '$CI_COMMIT_TAG'
64 when: never
65 - when: on_success
57 - !reference [.ignore-on-build, rules]
58 - !reference [.ignore-on-tag, rules]
59 - !reference [.pipeline-control-test, rules]
60 - when: on_success
6661
6762 .sqlite_test_nix_base:
6863 tags:
9287 - job: build_and_push_to_cachix
9388 artifacts: false
9489 rules:
95 - if: $BUILD_TEST
96 when: never
97 - if: '$CI_COMMIT_TAG'
98 when: never
99 - if: '$FULL_TEST || $DAILY_TEST'
100 when: on_success
90 - !reference [.ignore-on-build, rules]
91 - !reference [.ignore-on-tag, rules]
92 - !reference [.pipeline-control-test, rules]
10193 - when: on_success
10294
10395 sqlite_test_nix:
77 strategy: depend
88 rules:
99 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master)$/'
10 variables:
11 DISPATCHER_REF: master
12 when: on_success
13 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(staging)$/'
14 variables:
15 DISPATCHER_REF: staging
1016 when: on_success
1117 - if: '$INTEGRATION || $FULL_TEST || $DAILY_TEST'
18 variables:
19 DISPATCHER_REF: staging
1220 when: on_success
1321 - when: never
88 - git config --global user.name "Mergerbot"
99 - python3 scripts/merge-conflict-detector.py
1010 rules:
11 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master|dev)$/'
12 when: on_success
13 - if: '$CI_COMMIT_TAG'
14 when: never
11 - !reference [.dev-staging-master, rules]
12 - !reference [.ignore-on-tag, rules]
1513 - when: never
1614
1715 sanity_check:
2321 - bash scripts/sanity_check_commit.sh
2422 - scripts/sanity_check_file.py --mode=ls
2523 rules:
26 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master|dev)$/'
27 when: on_success
28 - if: '$CI_COMMIT_TAG'
29 when: never
24 - !reference [.dev-staging-master, rules]
25 - !reference [.ignore-on-tag, rules]
3026 - when: never
3127
3228 migration_sanity_check:
4036 - cd faraday
4137 - $(alembic branches)
4238 rules:
43 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master|dev)$/'
44 when: always
45 - if: '$CI_COMMIT_TAG'
46 when: never
39 - !reference [.dev-staging-master, rules]
40 - !reference [.ignore-on-tag, rules]
4741 - when: never
4842
4943 bandit:
5953 - "bandit -r ${CI_PROJECT_DIR}/faraday --format custom --skip B101 --msg-template \
6054 \"{abspath}:{line}: {test_id}[bandit]: {severity}: {msg}\""
6155 rules:
62 - if: '$CI_COMMIT_TAG'
63 when: never
56 - !reference [.ignore-on-tag, rules]
6457 - when: on_success
6558
6659 build_and_push_to_cachix:
8376 - nix-build | cachix push faradaysec
8477 - ./scripts/check-closure-size ./result
8578 rules:
86 - if: '$FULL_TEST || $DAILY_TEST'
87 when: on_success
88 - when: always
79 - when: on_success
8980
9081 flake8:
9182 image: python:3
9485 - pip install flake8
9586 - flake8 .
9687 rules:
97 - if: '$CI_COMMIT_TAG'
98 when: never
88 - !reference [.ignore-on-tag, rules]
9989 - when: on_success
10090
10191 no-format-str:
10292 image: python:3
10393 stage: pre_testing
10494 script:
105 - pip install flynt
95 - pip install flynt==0.69
10696 - flynt -df faraday tests
10797 rules:
108 - if: '$CI_COMMIT_TAG'
109 when: never
98 - !reference [.ignore-on-tag, rules]
99 - !reference [.ignore-on-master, rules]
110100 - when: on_success
3131 - pip freeze
3232 allow_failure: true
3333 rules:
34 #- if: '$FULL_TEST || $DAILY_TEST || $ALPHA_TEST'
35 - if: '$ALPHA_TEST' # FOR NOW, ASKED TO NOT CHARGE CI WORKER
34 # - !reference [ .pipeline-control-test, rules ]
35 # - !reference [ .dev-staging-master, rules ]
36 - if: '$ALPHA_TEST'
3637 when: on_success
37 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master|dev)$/'
38 when: never # FOR NOW, ASKED TO NOT CHARGE CI WORKER
39 #when: on_success
4038 - when: never
4139
4240
5553 - pip freeze
5654 allow_failure: true
5755 rules:
58 #- if: '$FULL_TEST || $DAILY_TEST || $ALPHA_TEST'
59 - if: '$ALPHA_TEST' # FOR NOW, ASKED TO FIX #6474 first
56 # - !reference [ .pipeline-control-test, rules ]
57 # - !reference [ .dev-staging-master, rules ]
58 - if: '$ALPHA_TEST'
6059 when: on_success
61 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master|dev)$/'
62 when: never # FOR NOW, ASKED TO FIX #6474 first
63 #when: on_success
6460 - when: never
6561
6662 unit_test 3.7:
7369
7470 unit_test 3.9:
7571 extends: .latest_unit_test_base
76 image: python:3.9-rc
72 image: python:3.9
73
74 unit_test 3.10:
75 extends: .latest_unit_test_base
76 image: python:3.10
7777
7878 alpha_unit_test 3.7:
7979 extends: .alpha_unit_test_base
8383 extends: .alpha_unit_test_base
8484 image: python:3.8
8585 rules:
86 #- if: '$FULL_TEST || $DAILY_TEST || $ALPHA_TEST'
87 - if: '$ALPHA_TEST'
88 when: on_success
89 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master|dev)$/'
90 when: never #on_success REACTIVATE WHEN HAVE TIME TO CHECK
91 - when: never
86 # - !reference [ .pipeline-control-test, rules ]
87 # - !reference [ .dev-staging-master, rules ]
88 - if: '$ALPHA_TEST'
89 when: on_success
90 - when: never
9291
9392 alpha_unit_test 3.9:
9493 extends: .alpha_unit_test_base
95 image: python:3.9-rc
94 image: python:3.9
95
96
97 alpha_unit_test 3.10:
98 extends: .alpha_unit_test_base
99 image: python:3.10
3434 - *google_storage_deb_rpm_base
3535 - "gsutil setmeta -h x-goog-meta-branch:${CI_COMMIT_BRANCH} ${GCLOUD_FILE_PATH}*.*"
3636 rules:
37 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master)$/'
38 when: on_success
39 - if: '$BUILD_TEST || $FULL_TEST || $DAILY_TEST'
40 when: on_success
37 - !reference [ .on-master-staging, rules ]
38 - !reference [ .be-uploaded, rules ]
4139 - when: never
4240 needs:
4341 - job: generate_deb
+0
-14
.gitlab/ci/upload/.testing-gitlab-ci.yml less more
0 .qa_integration:
1 stage: upload_testing
2 variables:
3 REMOTE_BRANCH: $CI_COMMIT_REF_NAME
4 MAIN_COMMIT_SHA: $CI_COMMIT_SHA
5 trigger:
6 project: faradaysec/qa/automation
7 strategy: depend
8 branch: develop
9 rules:
10 - if: '$CI_COMMIT_REF_NAME =~ /^.*\/(master)$/'
11 when: on_success
12 - if: '$CI_COMMIT_TAG || $BUILD_TEST || $FULL_TEST || $DAILY_TEST'
13 when: on_success
3030
3131 include:
3232 - local: .gitlab/ci/fetch-secrets.yaml
33 - local: .gitlab/ci/.rules-conditions.yml
34
35
3336 - local: .gitlab/ci/testing/.pretesting-gitlab-ci.yml
3437 - local: .gitlab/ci/testing/.nix-testing-gitlab-ci.yml
3538 - local: .gitlab/ci/testing/.venv-testing-gitlab-ci.yml
4144 - local: .gitlab/ci/build-ci/.testing-gitlab-ci.yml
4245
4346 - local: .gitlab/ci/upload/.storage-gitlab-ci.yml
44 - local: .gitlab/ci/upload/.testing-gitlab-ci.yml
4547
4648 - local: .gitlab/ci/deploy/deploy-gitlab-ci.yml
4749
4850 - local: .gitlab/ci/publish/.set-tag-gitlab-ci.yml
51 - local: .gitlab/ci/publish/.mirror-to-github-gitlab-ci.yml
4952 - local: .gitlab/ci/publish/.docker-publish-gitlab-ci.yml
5053
5154 - template: Security/Secret-Detection.gitlab-ci.yml
1010 exclude: '^faraday/server/www/'
1111 - id: check-yaml
1212 exclude: '^faraday/server/www/'
13 args: [ --unsafe ]
1314 - id: debug-statements
1415 exclude: '^faraday/server/www/'
1516 - repo: https://gitlab.com/pycqa/flake8
4748 pass_filenames: false
4849 args: [--mode=ls, --local]
4950 stages: [push]
51 - repo: https://github.com/asottile/pyupgrade
52 rev: v2.29.0
53 hooks:
54 - id: pyupgrade
55 args: [ --py3-plus , --py36-plus]
0 * ADD v3 bulks endpoints DELETE and EDIT (PATCH)
1 * Add logs of loggin, logout and log error to main log
2 * Fix bug in bulk update for m2m fields
3 * ADD clear settings command
4 * Add open medium, high and critical vulns histogram
5 * Fix integrity constraint error on cve update
6 * FIX static content for react
7 * Add cvss within vulnerability model
8 * add check to see if workspace name is longer than 250 characters. In that case raises an error
9 * change concat in urlstrings for join or urljoin
10 * Add cve to csv export
0 Dec 27th, 2021
11 =====================================
22
33
4 3.19.0 [Dec 27th, 2021]:
5 ---
6 * ADD v3 bulks endpoints DELETE and EDIT (PATCH)
7 * Add logs of loggin, logout and log error to main log
8 * Fix bug in bulk update for m2m fields
9 * ADD clear settings command
10 * Add open medium, high and critical vulns histogram
11 * Fix integrity constraint error on cve update
12 * FIX static content for react
13 * Add cvss within vulnerability model
14 * add check to see if workspace name is longer than 250 characters. In that case raises an error
15 * change concat in urlstrings for join or urljoin
16 * Add cve to csv export
17
418 3.18.1 [Nov 5th, 2021]:
519 ---
620 Fix CVE issue
923 ---
1024 * Remove attachments in vulns filter endpoint
1125 * Add open and confirmed vulns in workspace stats
12 * Add migration disabling several notifications.
1326 * Add user id to session API endpoint
1427 * Add cve to vulnerability model
1528 * Change funcs to views
1629 * FIX report import
1730 * Add `last_run_agent_date` field to workspace endpoint
1831 * Fix cve parsing in `vulnerability create` and `bulk create`
19 * ADD check if postgres db is running during server start
2032 * Fix order_by in filters api
2133 * Fix 500 status code with invalid executor arguments
2234
0 # -*- coding: utf-8 -*-
10 #
21 # Faraday documentation build configuration file, created by
32 # sphinx-quickstart on Tue Oct 31 19:10:26 2017.
4544 master_doc = 'index'
4645
4746 # General information about the project.
48 project = u'Faraday'
49 copyright = u'2017, Daniel Foguelman, Esteban Guillardoy, Ezequiel Tavella, Facundo de Guzmán, Federico Kirschbaum, Francisco Amato, Franco Linares, German Riera, Joaquín López Pereyra, Leonardo Lazzaro, Martín Rocha, Matias Ariel Ré Medina, Matias Lang, Micaela Ranea Sánchez, Sebastian Kulesz'
50 author = u'Daniel Foguelman, Esteban Guillardoy, Ezequiel Tavella, Facundo de Guzmán, Federico Kirschbaum, Francisco Amato, Franco Linares, German Riera, Joaquín López Pereyra, Leonardo Lazzaro, Martín Rocha, Matias Ariel Ré Medina, Matias Lang, Micaela Ranea Sánchez, Sebastian Kulesz'
47 project = 'Faraday'
48 copyright = '2017, Daniel Foguelman, Esteban Guillardoy, Ezequiel Tavella, Facundo de Guzmán, Federico Kirschbaum, Francisco Amato, Franco Linares, German Riera, Joaquín López Pereyra, Leonardo Lazzaro, Martín Rocha, Matias Ariel Ré Medina, Matias Lang, Micaela Ranea Sánchez, Sebastian Kulesz'
49 author = 'Daniel Foguelman, Esteban Guillardoy, Ezequiel Tavella, Facundo de Guzmán, Federico Kirschbaum, Francisco Amato, Franco Linares, German Riera, Joaquín López Pereyra, Leonardo Lazzaro, Martín Rocha, Matias Ariel Ré Medina, Matias Lang, Micaela Ranea Sánchez, Sebastian Kulesz'
5150
5251 # The version info for the project you're documenting, acts as replacement for
5352 # |version| and |release|, also used in various other places throughout the
5453 # built documents.
5554 #
5655 # The short X.Y version.
57 version = u'3.0.0'
56 version = '3.0.0'
5857 # The full version, including alpha/beta/rc tags.
59 release = u'3.0.0'
58 release = '3.0.0'
6059
6160 # The language for content autogenerated by Sphinx. Refer to documentation
6261 # for a list of supported languages.
141140 # (source start file, target name, title,
142141 # author, documentclass [howto, manual, or own class]).
143142 latex_documents = [
144 (master_doc, 'Faraday.tex', u'Faraday Documentation',
145 u'Daniel Foguelman, Esteban Guillardoy, Ezequiel Tavella, Facundo de Guzmán, Federico Kirschbaum, Francisco Amato, Franco Linares, German Riera, Joaquín López Pereyra, Leonardo Lazzaro, Martín Rocha, Matias Ariel Ré Medina, Matias Lang, Micaela Ranea Sánchez, Sebastian Kulesz', 'manual'),
143 (master_doc, 'Faraday.tex', 'Faraday Documentation',
144 'Daniel Foguelman, Esteban Guillardoy, Ezequiel Tavella, Facundo de Guzmán, Federico Kirschbaum, Francisco Amato, Franco Linares, German Riera, Joaquín López Pereyra, Leonardo Lazzaro, Martín Rocha, Matias Ariel Ré Medina, Matias Lang, Micaela Ranea Sánchez, Sebastian Kulesz', 'manual'),
146145 ]
147146
148147
151150 # One entry per manual page. List of tuples
152151 # (source start file, name, description, authors, manual section).
153152 man_pages = [
154 (master_doc, 'faraday', u'Faraday Documentation',
153 (master_doc, 'faraday', 'Faraday Documentation',
155154 [author], 1)
156155 ]
157156
162161 # (source start file, target name, title, author,
163162 # dir menu entry, description, category)
164163 texinfo_documents = [
165 (master_doc, 'Faraday', u'Faraday Documentation',
164 (master_doc, 'Faraday', 'Faraday Documentation',
166165 author, 'Faraday', 'One line description of project.',
167166 'Miscellaneous'),
168167 ]
169
170
171
172 # I'm Py3
11 # Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/)
22 # See the file 'doc/LICENSE' for the license information
33
4 __version__ = '3.18.1'
4 __version__ = '3.19.0'
55 __license_version__ = __version__
143143 except OperationalError:
144144 logger = logging.getLogger(__name__)
145145 logger.error(
146 ('Could not connect to PostgreSQL. Please check: '
147 'if database is running or if the configuration settings are correct.')
146 'Could not connect to PostgreSQL. Please check: '
147 'if database is running or if the configuration settings are correct.'
148148 )
149149 sys.exit(1)
150150
199199 if not conn_string:
200200 logger = logging.getLogger(__name__)
201201 logger.error(
202 ('No database configuration found. Please check: '
202 'No database configuration found. Please check: '
203203 'if the database is running or if the configuration settings are correct. '
204 'For first time installations execute: faraday-manage initdb')
204 'For first time installations execute: faraday-manage initdb'
205205 )
206206 sys.exit(1)
207207 InitDB()._create_tables(conn_string)
280280
281281
282282 @click.command(help="Manage settings")
283 @click.option('-a', '--action', type=click.Choice(['show', 'update', 'list'], case_sensitive=False),
283 @click.option('-a', '--action', type=click.Choice(['show', 'update', 'list', 'clear'], case_sensitive=False),
284284 default='list', show_default=True, help="Action")
285285 @click.option('--data', type=str, required=False, callback=manage_settings.settings_format_validation,
286286 help="Settings config in json")
308308
309309 if __name__ == '__main__':
310310 cli()
311
312 # I'm Py3
0
10 import logging
21 import faraday.server.config
32 from faraday.server.web import get_app
7877 run_migrations_offline()
7978 else:
8079 run_migrations_online()
81 # I'm Py3
183183 op.drop_table('rule_action')
184184 op.drop_table('action')
185185 op.drop_table('rule')
186
187
188 # I'm Py3
6666 op.drop_table('notification')
6767 # op.drop_constraint(None, 'notification_user_id_fkey', type_='foreignkey')
6868 # op.drop_constraint(None, 'notification_workspace_id_fkey', type_='foreignkey')
69 # I'm Py3
0 """Severities histogram model
1
2 Revision ID: 15d70093d262
3 Revises: d8f0b32a5c0e
4 Create Date: 2021-11-08 13:57:28.099487+00:00
5
6 """
7 from alembic import op
8 import sqlalchemy as sa
9
10
11 # revision identifiers, used by Alembic.
12 from sqlalchemy import func, case
13
14 from faraday.server.models import VulnerabilityGeneric, SeveritiesHistogram, Workspace
15
16 revision = '15d70093d262'
17 down_revision = 'd8f0b32a5c0e'
18 branch_labels = None
19 depends_on = None
20
21
22 def upgrade():
23 # ### commands auto generated by Alembic - please adjust! ###
24 op.create_table('severities_histogram',
25 sa.Column('id', sa.Integer(), nullable=False),
26 sa.Column('workspace_id', sa.Integer(), nullable=False),
27 sa.Column('date', sa.Date(), nullable=False),
28 sa.Column('medium', sa.Integer(), nullable=False),
29 sa.Column('high', sa.Integer(), nullable=False),
30 sa.Column('critical', sa.Integer(), nullable=False),
31 sa.Column('confirmed', sa.Integer(), nullable=False),
32 sa.ForeignKeyConstraint(['workspace_id'], ['workspace.id'], ),
33 sa.PrimaryKeyConstraint('id')
34 )
35 op.create_index(op.f('ix_severities_histogram_workspace_id'), 'severities_histogram', ['workspace_id'], unique=False)
36 # ### end Alembic commands ###
37
38 # Init histogram
39 bind = op.get_bind()
40 session = sa.orm.Session(bind=bind)
41 workspaces = session.query(Workspace).all()
42 for workspace in workspaces:
43 vulnerabilities = session.query(VulnerabilityGeneric) \
44 .with_entities(func.date_trunc('day', VulnerabilityGeneric.create_date),
45 VulnerabilityGeneric.severity,
46 func.count(VulnerabilityGeneric.severity),
47 func.sum(case([(VulnerabilityGeneric.confirmed, 1)], else_=0)))\
48 .filter(VulnerabilityGeneric.workspace_id == workspace.id,
49 VulnerabilityGeneric.status.notin_(['closed', 'risk-accepted']),
50 VulnerabilityGeneric.severity.in_(['medium', 'high', 'critical']))\
51 .group_by(func.date_trunc('day', VulnerabilityGeneric.create_date), VulnerabilityGeneric.severity).all()
52 for histogram_date, severity_type, severity_count, confirmed_count in vulnerabilities:
53 severity_histogram = session.query(SeveritiesHistogram)\
54 .filter(SeveritiesHistogram.date == histogram_date,
55 SeveritiesHistogram.workspace_id == workspace.id).first()
56 if severity_histogram is None:
57 severity_histogram = SeveritiesHistogram(date=histogram_date, workspace=workspace, medium=0, high=0, critical=0, confirmed=0)
58 session.add(severity_histogram)
59 session.commit()
60 if severity_type == 'medium':
61 severity_histogram.medium = severity_count
62 if severity_type == 'high':
63 severity_histogram.high = severity_count
64 if severity_type == 'critical':
65 severity_histogram.critical = severity_count
66 severity_histogram.confirmed += confirmed_count
67 session.commit()
68
69
70 def downgrade():
71 # ### commands auto generated by Alembic - please adjust! ###
72 op.drop_index(op.f('ix_severities_histogram_workspace_id'), table_name='severities_histogram')
73 op.drop_table('severities_histogram')
74 # ### end Alembic commands ###
7272 'json_data': json.dumps(custom_fields),
7373 'vuln_id': vuln_id
7474 })
75
76 # I'm Py3
4444
4545 def downgrade():
4646 # Convert 'asset_owner' status into 'client'
47 op.execute(cmd.update().where(cmd.c.import_source == u'agent')
47 op.execute(cmd.update().where(cmd.c.import_source == 'agent')
4848 .values(import_source=None))
4949 # Create a temporary "_role" type, convert and drop the "new" type
5050 tmp_type.create(op.get_bind(), checkfirst=False)
2121
2222 def downgrade():
2323 op.drop_column('workspace', 'readonly')
24 # I'm Py3
2222 def downgrade():
2323 conn = op.get_bind()
2424 conn.execute('ALTER TABLE custom_fields_schema DROP CONSTRAINT custom_fields_schema_field_name_key;')
25 # I'm Py3
0 """empty message
1
2 Revision ID: 38bb251889e6
3 Revises: 15d70093d262
4 Create Date: 2021-07-30 02:12:00.706416+00:00
5
6 """
7 from alembic import op
8
9 # revision identifiers, used by Alembic.
10 revision = '38bb251889e6'
11 down_revision = '15d70093d262'
12 branch_labels = None
13 depends_on = None
14
15
16 def upgrade():
17
18 # Agent table
19 op.drop_constraint('executor_agent_id_fkey', 'executor')
20 op.create_foreign_key(
21 'executor_agent_id_fkey',
22 'executor',
23 'agent', ['agent_id'], ['id'],
24 ondelete='CASCADE'
25 )
26 op.drop_constraint('association_workspace_and_agents_table_agent_id_fkey',
27 'association_workspace_and_agents_table')
28 op.create_foreign_key(
29 'association_workspace_and_agents_table_agent_id_fkey',
30 'association_workspace_and_agents_table',
31 'agent', ['agent_id'], ['id'],
32 ondelete='CASCADE'
33 )
34
35 # Vulnerability_template table
36 op.drop_constraint('knowledge_base_vulnerability_template_id_fkey',
37 'knowledge_base')
38 op.create_foreign_key(
39 'knowledge_base_vulnerability_template_id_fkey', 'knowledge_base',
40 'vulnerability_template', ['vulnerability_template_id'], ['id'],
41 ondelete='CASCADE'
42 )
43
44 # Comment table
45 op.drop_constraint('comment_reply_to_id_fkey',
46 'comment')
47 op.create_foreign_key(
48 'comment_reply_to_id_fkey', 'comment',
49 'comment', ['reply_to_id'], ['id'],
50 ondelete='SET NULL'
51 )
52
53 # Service table
54 op.drop_constraint('credential_service_id_fkey', 'credential')
55 op.create_foreign_key(
56 'credential_service_id_fkey', 'credential',
57 'service', ['service_id'], ['id'],
58 ondelete='CASCADE'
59 )
60
61 # Command table
62 op.drop_constraint('command_object_command_id_fkey', 'command_object')
63 op.create_foreign_key(
64 'command_object_command_id_fkey', 'command_object',
65 'command', ['command_id'], ['id'],
66 ondelete='SET NULL'
67 )
68 op.drop_constraint('agent_execution_command_id_fkey', 'agent_execution')
69 op.create_foreign_key(
70 'agent_execution_command_id_fkey', 'agent_execution',
71 'command', ['command_id'], ['id'],
72 ondelete='SET NULL'
73 )
74 op.drop_constraint('rule_execution_command_id_fkey', 'rule_execution')
75 op.create_foreign_key(
76 'rule_execution_command_id_fkey', 'rule_execution',
77 'command', ['command_id'], ['id'],
78 ondelete='CASCADE'
79 )
80
81 # Host table
82 op.drop_constraint('hostname_host_id_fkey', 'hostname')
83 op.create_foreign_key(
84 'hostname_host_id_fkey', 'hostname',
85 'host', ['host_id'], ['id'],
86 ondelete='CASCADE'
87 )
88 op.drop_constraint('service_host_id_fkey', 'service')
89 op.create_foreign_key(
90 'service_host_id_fkey', 'service',
91 'host', ['host_id'], ['id'],
92 ondelete='CASCADE'
93 )
94 op.drop_constraint('vulnerability_host_id_fkey', 'vulnerability')
95 op.create_foreign_key(
96 'vulnerability_host_id_fkey', 'vulnerability',
97 'host', ['host_id'], ['id'],
98 ondelete='CASCADE'
99 )
100 op.drop_constraint('credential_host_id_fkey', 'credential')
101 op.create_foreign_key(
102 'credential_host_id_fkey', 'credential',
103 'host', ['host_id'], ['id'],
104 ondelete='CASCADE'
105 )
106
107 # Vulnerability Table
108 op.drop_constraint('vulnerability_vulnerability_duplicate_id_fkey', 'vulnerability')
109 op.create_foreign_key(
110 'vulnerability_vulnerability_duplicate_id_fkey', 'vulnerability',
111 'vulnerability', ['vulnerability_duplicate_id'], ['id'],
112 ondelete='SET NULL'
113 )
114
115 # VulnerabilityTemplate Table
116 op.drop_constraint('vulnerability_vulnerability_template_id_fkey', 'vulnerability')
117 op.create_foreign_key(
118 'vulnerability_vulnerability_template_id_fkey', 'vulnerability',
119 'vulnerability_template', ['vulnerability_template_id'], ['id'],
120 ondelete='SET NULL'
121 )
122
123 # SourceCode Table
124 op.drop_constraint('vulnerability_source_code_id_fkey', 'vulnerability')
125 op.create_foreign_key(
126 'vulnerability_source_code_id_fkey', 'vulnerability',
127 'source_code', ['source_code_id'], ['id'],
128 ondelete='CASCADE'
129 )
130
131
132 def downgrade():
133
134 # Agent table
135 op.drop_constraint('executor_agent_id_fkey',
136 'executor')
137 op.create_foreign_key(
138 'executor_agent_id_fkey',
139 'executor',
140 'agent', ['agent_id'], ['id']
141 )
142 op.drop_constraint('association_workspace_and_agents_table_agent_id_fkey',
143 'association_workspace_and_agents_table')
144 op.create_foreign_key(
145 'association_workspace_and_agents_table_agent_id_fkey',
146 'association_workspace_and_agents_table',
147 'agent', ['agent_id'], ['id']
148 )
149
150 # Vulnerability_template table
151 op.drop_constraint('knowledge_base_vulnerability_template_id_fkey',
152 'knowledge_base')
153 op.create_foreign_key(
154 'knowledge_base_vulnerability_template_id_fkey', 'knowledge_base',
155 'vulnerability_template', ['vulnerability_template_id'], ['id']
156 )
157
158 # Comment table
159 op.drop_constraint('comment_reply_to_id_fkey',
160 'comment')
161 op.create_foreign_key(
162 'comment_reply_to_id_fkey', 'comment',
163 'comment', ['reply_to_id'], ['id']
164 )
165
166 # Service table
167 op.drop_constraint('credential_service_id_fkey', 'credential')
168 op.create_foreign_key(
169 'credential_service_id_fkey', 'credential',
170 'service', ['service_id'], ['id']
171 )
172
173 # Command table
174 op.drop_constraint('command_object_command_id_fkey', 'command_object')
175 op.create_foreign_key(
176 'command_object_command_id_fkey', 'command_object',
177 'command', ['command_id'], ['id']
178 )
179 op.drop_constraint('agent_execution_command_id_fkey', 'agent_execution')
180 op.create_foreign_key(
181 'agent_execution_command_id_fkey', 'agent_execution',
182 'command', ['command_id'], ['id']
183 )
184 op.drop_constraint('rule_execution_command_id_fkey', 'rule_execution')
185 op.create_foreign_key(
186 'rule_execution_command_id_fkey', 'rule_execution',
187 'command', ['command_id'], ['id']
188 )
189
190 # Host table
191 op.drop_constraint('credential_host_id_fkey', 'credential')
192 op.create_foreign_key(
193 'credential_host_id_fkey', 'credential',
194 'host', ['host_id'], ['id']
195 )
196 op.drop_constraint('hostname_host_id_fkey', 'hostname')
197 op.create_foreign_key(
198 'hostname_host_id_fkey', 'hostname',
199 'host', ['host_id'], ['id']
200 )
201 op.drop_constraint('service_host_id_fkey', 'service')
202 op.create_foreign_key(
203 'service_host_id_fkey', 'service',
204 'host', ['host_id'], ['id']
205 )
206 op.drop_constraint('vulnerability_host_id_fkey', 'vulnerability')
207 op.create_foreign_key(
208 'vulnerability_host_id_fkey', 'vulnerability',
209 'host', ['host_id'], ['id']
210 )
211
212 op.drop_constraint('vulnerability_vulnerability_duplicate_id_fkey', 'vulnerability')
213 op.create_foreign_key(
214 'vulnerability_vulnerability_duplicate_id_fkey', 'vulnerability',
215 'vulnerability', ['vulnerability_duplicate_id'], ['id']
216 )
217
218 # VulnerabilityTemplate Table
219 op.drop_constraint('vulnerability_vulnerability_template_id_fkey', 'vulnerability')
220 op.create_foreign_key(
221 'vulnerability_vulnerability_template_id_fkey', 'vulnerability',
222 'vulnerability_template', ['vulnerability_template_id'], ['id']
223 )
224
225 # SourceCode Table
226 op.drop_constraint('vulnerability_source_code_id_fkey', 'vulnerability')
227 op.create_foreign_key(
228 'vulnerability_source_code_id_fkey', 'vulnerability',
229 'source_code', ['source_code_id'], ['id']
230 )
2121
2222 def downgrade():
2323 op.drop_column('executive_report', 'markdown')
24 # I'm Py3
2525 conn = op.get_bind()
2626 conn.execute('ALTER TABLE vulnerability DROP COLUMN external_id')
2727 conn.execute('ALTER TABLE vulnerability_template DROP COLUMN external_id')
28
29
30 # I'm Py3
0 """PolicyViolationVulnerabilityAssociation FK ondelete action
1
2 Revision ID: 5cf9660bba80
3 Revises: 7dea3a6caf51
4 Create Date: 2021-12-01 16:09:40.318964+00:00
5
6 """
7 from alembic import op
8 import sqlalchemy as sa
9
10
11 # revision identifiers, used by Alembic.
12 revision = '5cf9660bba80'
13 down_revision = '7dea3a6caf51'
14 branch_labels = None
15 depends_on = None
16
17
18 def upgrade():
19 # ### commands auto generated by Alembic - please adjust! ###
20 op.drop_constraint('policy_violation_vulnerability_associatio_vulnerability_id_fkey', 'policy_violation_vulnerability_association', type_='foreignkey')
21 op.create_foreign_key(None, 'policy_violation_vulnerability_association', 'vulnerability', ['vulnerability_id'], ['id'], ondelete='CASCADE')
22 op.alter_column('vulnerability', 'risk',
23 existing_type=sa.REAL(),
24 type_=sa.Float(precision=3, asdecimal=1),
25 existing_nullable=True)
26 op.alter_column('vulnerability_template', 'risk',
27 existing_type=sa.REAL(),
28 type_=sa.Float(precision=3, asdecimal=1),
29 existing_nullable=True)
30 # ### end Alembic commands ###
31
32
33 def downgrade():
34 # ### commands auto generated by Alembic - please adjust! ###
35 op.alter_column('vulnerability_template', 'risk',
36 existing_type=sa.Float(precision=3, asdecimal=1),
37 type_=sa.REAL(),
38 existing_nullable=True)
39 op.alter_column('vulnerability', 'risk',
40 existing_type=sa.Float(precision=3, asdecimal=1),
41 type_=sa.REAL(),
42 existing_nullable=True)
43 op.drop_constraint('policy_violation_vulnerability_associatio_vulnerability_id_fkey', 'policy_violation_vulnerability_association', type_='foreignkey')
44 op.create_foreign_key(None, 'policy_violation_vulnerability_association', 'vulnerability', ['vulnerability_id'], ['id'])
45 # ### end Alembic commands ###
0 """cascade in vuls relation
1
2 Revision ID: 7dea3a6caf51
3 Revises: 38bb251889e6
4 Create Date: 2021-11-10 21:23:24.837776+00:00
5
6 """
7 from alembic import op
8 import sqlalchemy as sa
9
10
11 # revision identifiers, used by Alembic.
12 revision = '7dea3a6caf51'
13 down_revision = '38bb251889e6'
14 branch_labels = None
15 depends_on = None
16
17
18 def upgrade():
19 # ### commands auto generated by Alembic - please adjust! ###
20 op.drop_constraint('command_workspace_id_fkey', 'command', type_='foreignkey')
21 op.create_foreign_key(None, 'command', 'workspace', ['workspace_id'], ['id'], ondelete='CASCADE')
22 op.drop_constraint('knowledge_base_vulnerability_template_id_fkey', 'knowledge_base', type_='foreignkey')
23 op.create_foreign_key(None, 'knowledge_base', 'vulnerability_template', ['vulnerability_template_id'], ['id'])
24 op.drop_constraint('reference_vulnerability_association_vulnerability_id_fkey', 'reference_vulnerability_association', type_='foreignkey')
25 op.create_foreign_key(None, 'reference_vulnerability_association', 'vulnerability', ['vulnerability_id'], ['id'], ondelete='CASCADE')
26 op.alter_column('vulnerability', 'risk',
27 existing_type=sa.REAL(),
28 type_=sa.Float(precision=3, asdecimal=1),
29 existing_nullable=True)
30 op.drop_constraint('vulnerability_service_id_fkey', 'vulnerability', type_='foreignkey')
31 op.create_foreign_key(None, 'vulnerability', 'service', ['service_id'], ['id'], ondelete='CASCADE')
32 op.alter_column('vulnerability_template', 'risk',
33 existing_type=sa.REAL(),
34 type_=sa.Float(precision=3, asdecimal=1),
35 existing_nullable=True)
36 # ### end Alembic commands ###
37
38
39 def downgrade():
40 # ### commands auto generated by Alembic - please adjust! ###
41 op.alter_column('vulnerability_template', 'risk',
42 existing_type=sa.Float(precision=3, asdecimal=1),
43 type_=sa.REAL(),
44 existing_nullable=True)
45 op.drop_constraint('vulnerability_service_id_fkey', 'vulnerability', type_='foreignkey')
46 op.create_foreign_key(None, 'vulnerability', 'service', ['service_id'], ['id'])
47 op.alter_column('vulnerability', 'risk',
48 existing_type=sa.Float(precision=3, asdecimal=1),
49 type_=sa.REAL(),
50 existing_nullable=True)
51 op.drop_constraint('reference_vulnerability_association_vulnerability_id_fkey', 'reference_vulnerability_association', type_='foreignkey')
52 op.create_foreign_key(None, 'reference_vulnerability_association', 'vulnerability', ['vulnerability_id'], ['id'])
53 op.drop_constraint('knowledge_base_vulnerability_template_id_fkey', 'knowledge_base', type_='foreignkey')
54 op.create_foreign_key(None, 'knowledge_base', 'vulnerability_template', ['vulnerability_template_id'], ['id'], ondelete='CASCADE')
55 op.drop_constraint('command_workspace_id_fkey', 'command', type_='foreignkey')
56 op.create_foreign_key(None, 'command', 'workspace', ['workspace_id'], ['id'])
57 # ### end Alembic commands ###
2929 op.drop_column('faraday_user', 'otp_secret')
3030 op.drop_column('faraday_user', 'state_otp')
3131 op.execute('DROP TYPE user_otp_states')
32 # I'm Py3
7979 def downgrade():
8080 op.drop_table('agent_schedule')
8181 op.drop_table('agent')
82
83
84 # I'm Py3
2323 def downgrade():
2424 conn = op.get_bind()
2525 conn.execute('ALTER TABLE executive_report DROP COLUMN filter')
26
27
28 # I'm Py3
0 """cvss_model
1
2 Revision ID: d8f0b32a5c0e
3 Revises: f28eae25416b
4 Create Date: 2021-09-01 10:30:06.693843+00:00
5
6 """
7 from alembic import op
8 import sqlalchemy as sa
9
10
11 # revision identifiers, used by Alembic.
12 revision = 'd8f0b32a5c0e'
13 down_revision = 'f28eae25416b'
14 branch_labels = None
15 depends_on = None
16
17
18 def upgrade():
19 # ### commands auto generated by Alembic - please adjust! ###
20 op.create_table('cvss_base',
21 sa.Column('id', sa.Integer(), nullable=False),
22 sa.Column('version', sa.String(length=8), nullable=False),
23 sa.Column('vector_string', sa.String(length=64)),
24 sa.Column('type', sa.String(length=24), nullable=True),
25 sa.Column('base_score', sa.Float(), default=0.0),
26 sa.Column('fixed_base_score', sa.Float(), default=0.0),
27 sa.PrimaryKeyConstraint('id')
28 )
29 op.create_table('cvss_v2',
30 sa.Column('id', sa.Integer(), nullable=False),
31 sa.Column('access_vector', sa.Enum('N', 'A', 'L', name='cvss_access_vector')),
32 sa.Column('access_complexity', sa.Enum('L', 'M', 'H', name='cvss_access_complexity')),
33 sa.Column('authentication', sa.Enum('N', 'S', 'M', name='cvss_authentication')),
34 sa.Column('confidentiality_impact', sa.Enum('N', 'P', 'C', name='cvss_impact_types_v2')),
35 sa.Column('integrity_impact', sa.Enum('N', 'P', 'C', name='cvss_impact_types_v2')),
36 sa.Column('availability_impact', sa.Enum('N', 'P', 'C', name='cvss_impact_types_v2')),
37 sa.ForeignKeyConstraint(['id'], ['cvss_base.id'], ),
38 sa.PrimaryKeyConstraint('id')
39 )
40 op.create_table('cvss_v3',
41 sa.Column('id', sa.Integer(), nullable=False),
42 sa.Column('attack_vector', sa.Enum('N', 'A', 'L', 'P', name='cvss_attack_vector')),
43 sa.Column('attack_complexity', sa.Enum('L', 'H', name='cvss_attack_complexity')),
44 sa.Column('privileges_required', sa.Enum('N', 'L', 'H', name='cvss_privileges_required')),
45 sa.Column('user_interaction', sa.Enum('N', 'R', name='cvss_user_interaction')),
46 sa.Column('scope', sa.Enum('U', 'C', name='cvss_scope')),
47 sa.Column('confidentiality_impact', sa.Enum('N', 'L', 'H', name='cvss_impact_types_v3')),
48 sa.Column('integrity_impact', sa.Enum('N', 'L', 'H', name='cvss_impact_types_v3')),
49 sa.Column('availability_impact', sa.Enum('N', 'L', 'H', name='cvss_impact_types_v3')),
50 sa.ForeignKeyConstraint(['id'], ['cvss_base.id'], ),
51 sa.PrimaryKeyConstraint('id')
52 )
53
54 # Vuln relationship with cvss
55 op.add_column('vulnerability', sa.Column('cvssv2_id', sa.Integer(), nullable=True))
56 op.add_column('vulnerability', sa.Column('cvssv3_id', sa.Integer(), nullable=True))
57 op.create_foreign_key(None, 'vulnerability', 'cvss_v2', ['cvssv2_id'], ['id'])
58 op.create_foreign_key(None, 'vulnerability', 'cvss_v3', ['cvssv3_id'], ['id'])
59
60 # ### end Alembic commands ###
61
62
63 def downgrade():
64 # ### commands auto generated by Alembic - please adjust! ###
65 # op.drop_constraint(None, 'vulnerability', type_='foreignkey')
66 op.drop_column('vulnerability', 'cvssv2_id')
67 op.drop_column('vulnerability', 'cvssv3_id')
68 op.drop_table('cvss_v3')
69 op.drop_table('cvss_v2')
70 op.drop_table('cvss_base')
71 op.execute('drop type cvss_attack_complexity')
72 op.execute('drop type cvss_access_vector')
73 op.execute('drop type cvss_access_complexity')
74 op.execute('drop type cvss_attack_vector')
75 op.execute('drop type cvss_authentication')
76 op.execute('drop type cvss_privileges_required')
77 op.execute('drop type cvss_scope')
78 op.execute('drop type cvss_user_interaction')
79 op.execute('drop type cvss_impact_types_v2')
80 op.execute('drop type cvss_impact_types_v3')
81 #
82 # ### end Alembic commands ###
3333 conn.execute('ALTER TABLE vulnerability DROP COLUMN custom_fields')
3434 conn.execute('ALTER TABLE vulnerability_template DROP COLUMN custom_fields')
3535 conn.execute('DROP TABLE custom_fields_schema')
36 # I'm Py3
4949 old_type = sa.Enum(*ROLES, name='user_roles')
5050
5151 # Convert 'asset_owner' status into 'client'
52 op.execute(tcr.update().where(tcr.c.role == u'asset_owner')
52 op.execute(tcr.update().where(tcr.c.role == 'asset_owner')
5353 .values(status='client'))
5454 # Create a temporary "_role" type, convert and drop the "new" type
5555 tmp_type.create(op.get_bind(), checkfirst=False)
00 import json
11 import logging
22 import socket
3 from urllib.parse import urlencode
4
3 from urllib.parse import urlencode, urljoin, urlparse
54 from requests.adapters import ConnectionError, ReadTimeout
65
76 logger = logging.getLogger('Faraday searcher')
5655 raise UserWarning('Invalid username or password')
5756
5857 def _url(self, path, is_get=False):
59 url = self.base + 'v3/' + path
58 url = urljoin(self.base, f'v3/{path}')
6059 if self.command_id and 'commands' not in url and not url.endswith('}') and not is_get:
61 if '?' in url:
62 url += f'&command_id={self.command_id}'
63 elif url.endswith('/'):
64 url = f'{url[:-1]}?command_id={self.command_id}'
60 if url.endswith('/'):
61 url = urljoin(url[:-1], f'?command_id={self.command_id}')
6562 else:
66 url += f'?command_id={self.command_id}'
63 q = urlparse(url).query
64 url = urljoin(url, f'?{q}&command_id={self.command_id}')
6765 return url
6866
6967 def _get(self, url, object_name):
00 #!/usr/bin/env python
1 # -*- coding: utf-8 -*-
21
32 ###
43 # Faraday Penetration Test IDE
54 # Copyright (C) 2018 Infobyte LLC (http://www.infobytesec.com/)
65 # See the file 'doc/LICENSE' for the license information
76 ###
8 from builtins import str
97
108 import ast
119 import json
179177 set_array(field, value, add=to_add)
180178 action = f'Adding {value} to {key} list in vulnerability {vuln.name} with id {vuln.id}'
181179 if not to_add:
182 action = 'Removing %s from %s list in vulnerability %s with id %s' % (
180 action = 'Removing {} from {} list in vulnerability {} with id {}'.format(
183181 value, key, vuln.name, vuln.id)
184182
185183 logger.info(action)
216214 set_array(field, value, add=to_add)
217215 action = f'Adding {value} to {key} list in service {service.name} with id {service.id}'
218216 if not to_add:
219 action = 'Removing %s from %s list in service %s with id %s' % (
217 action = 'Removing {} from {} list in service {} with id {}'.format(
220218 value, key, service.name, service.id)
221219
222220 logger.info(action)
247245 set_array(field, value, add=to_add)
248246 action = f'Adding {value} to {key} list in host {host.name} with id {host.id}'
249247 if not to_add:
250 action = 'Removing %s from %s list in host %s with id %s' % (
248 action = 'Removing {} from {} list in host {} with id {}'.format(
251249 value, key, host.name, host.id)
252250
253251 logger.info(action)
366364 return rule
367365
368366 rule_str = json.dumps(rule)
369 r = re.findall("\{\{(.*?)\}\}", rule_str)
367 r = re.findall(r"\{\{(.*?)\}\}", rule_str)
370368 _vars = list(set(r))
371369 for var in _vars:
372370 value = value_item[var]
598596 action = action.strip('--')
599597 array = action.split(':')
600598 command = array[0]
601 expression = str(':').join(array[1:])
599 expression = ':'.join(array[1:])
602600
603601 if command == 'UPDATE':
604602 array_exp = expression.split('=')
605603 key = array_exp[0]
606 value = str('=').join(array_exp[1:])
604 value = '='.join(array_exp[1:])
607605 if object_type in ['Vulnerabilityweb', 'Vulnerability_web', 'Vulnerability']:
608606 self._update_vulnerability(obj, key, value)
609607
628626 else:
629627 if self.mail_notification:
630628 subject = 'Faraday searcher alert'
631 body = '%s %s have been modified by rule %s at %s' % (
629 body = '{} {} have been modified by rule {} at {}'.format(
632630 object_type, obj.name, rule['id'], str(datetime.utcnow()))
633631 self.mail_notification.send_mail(expression, subject, body)
634632 logger.info(f"Sending mail to: '{expression}'")
687685 if isinstance(field, str):
688686 setattr(vuln, key, value)
689687 logger.info(
690 "Changing property %s to %s in vulnerability '%s' with id %s" % (
688 "Changing property {} to {} in vulnerability '{}' with id {}".format(
691689 key, value, vuln.name, vuln.id))
692690 else:
693691 self.api.set_array(field, value, add=to_add, key=key, object=vuln)
694 action = 'Adding %s to %s list in vulnerability %s with id %s' % (
692 action = 'Adding {} to {} list in vulnerability {} with id {}'.format(
695693 value, key, vuln.name, vuln.id)
696694 if not to_add:
697 action = 'Removing %s from %s list in vulnerability %s with id %s' % (
695 action = 'Removing {} from {} list in vulnerability {} with id {}'.format(
698696 value, key, vuln.name, vuln.id)
699697
700698 logger.info(action)
702700 else:
703701 vuln.custom_fields[key] = value
704702 logger.info(
705 "Changing custom field %s to %s in vulnerability '%s' with id %s" % (
703 "Changing custom field {} to {} in vulnerability '{}' with id {}".format(
706704 key, value, vuln.name, vuln.id))
707705
708706 result = self.api.update_vulnerability(vuln)
728726 if isinstance(field, str):
729727 setattr(service, key, value)
730728 logger.info(
731 "Changing property %s to %s in service '%s' with id %s" % (
729 "Changing property {} to {} in service '{}' with id {}".format(
732730 key, value, service.name, service.id))
733731 else:
734732 self.api.set_array(field, value, add=to_add, key=key, object=service)
735733 action = f'Adding {value} to {key} list in service {service.name} with id {service.id}'
736734 if not to_add:
737 action = 'Removing %s from %s list in service %s with id %s' % (
735 action = 'Removing {} from {} list in service {} with id {}'.format(
738736 value, key, service.name, service.id)
739737
740738 logger.info(action)
764762 self.api.set_array(field, value, add=to_add, key=key, object=host)
765763 action = f'Adding {value} to {key} list in host {host.ip} with id {host.id}'
766764 if not to_add:
767 action = 'Removing %s from %s list in host %s with id %s' % (
765 action = 'Removing {} from {} list in host {} with id {}'.format(
768766 value, key, host.ip, host.id)
769767
770768 logger.info(action)
814812 signal.signal(signal.SIGINT, signal_handler)
815813
816814 loglevel = log
817 with open(rules, 'r') as rules_file:
815 with open(rules) as rules_file:
818816 try:
819817 rules = json.loads(rules_file.read())
820818 except Exception:
876874
877875 if __name__ == "__main__":
878876 main()
879 # I'm Py3
0
10 import json
21 import logging
32 import socket
00 #!/usr/bin/env python
1 # -*- coding: utf-8 -*-
21
32 ###
43 # Faraday Penetration Test IDE
8281
8382
8483 def validate_values(values, rule, rule_id):
85 r = re.findall("\{\{(.*?)\}\}", json.dumps(rule))
84 r = re.findall(r"\{\{(.*?)\}\}", json.dumps(rule))
8685 _vars = list(set(r))
8786 keys = []
8887 for index, item in enumerate(values):
118117
119118 if action.startswith('--ALERT:'):
120119 expression = action.strip('--ALERT:')
121 if expression == '' or re.match("^(.+\@.+\..+)$", expression) is None:
120 if expression == '' or re.match(r"^(.+\@.+\..+)$", expression) is None:
122121 return False
123122
124123 if action.startswith('--EXECUTE:'):
196195
197196 logger.info('<-- Rules OK')
198197 return True
199 # I'm Py3
200200 """
201201 return getattr(self.model_class, self.lookup_field)
202202
203 def _validate_object_id(self, object_id):
203 def _validate_object_id(self, object_id, raise_error=True):
204204 """
205205 By default, it validates the value of the lookup field set by the user
206206 in the URL by calling ``self.lookup_field_type(object_id)``.
210210 try:
211211 self.lookup_field_type(object_id)
212212 except ValueError:
213 flask.abort(404, 'Invalid format of lookup field')
213 if raise_error:
214 flask.abort(404, 'Invalid format of lookup field')
215 return False
216 return True
214217
215218 def _get_base_query(self):
216219 """Return the initial query all views should use
288291 obj = query.filter(self._get_lookup_field() == object_id).one()
289292 except NoResultFound:
290293 flask.abort(404, f'Object with id "{object_id}" not found')
294 return obj
295
296 def _get_objects(self, object_ids, eagerload=False, **kwargs):
297 """
298 Given the object_id and extra route params, get an instance of
299 ``self.model_class``
300 """
301 object_ids = [object_id for object_id in object_ids if self._validate_object_id(object_id, raise_error=False)]
302 if eagerload:
303 query = self._get_eagerloaded_query(**kwargs)
304 else:
305 query = self._get_base_query(**kwargs)
306 try:
307 obj = query.filter(self._get_lookup_field().in_(object_ids)).all()
308 except NoResultFound:
309 return []
291310 return obj
292311
293312 def _dump(self, obj, route_kwargs, **kwargs):
12221241 self._perform_update(object_id, obj, data, partial=True, **kwargs)
12231242
12241243 return self._dump(obj, kwargs), 200
1244
1245
1246 class BulkUpdateMixin:
1247 # These mixin should be merged with DeleteMixin after v2 is removed
1248
1249 @route('', methods=['PATCH'])
1250 def bulk_update(self, **kwargs):
1251 """
1252 ---
1253 tags: [{tag_name}]
1254 summary: "Update a group of {class_model} by ids."
1255 responses:
1256 204:
1257 description: Ok
1258 """
1259 # TODO BULK_UPDATE_SCHEMA
1260 if not flask.request.json or 'ids' not in flask.request.json:
1261 flask.abort(400)
1262 ids = list(filter(lambda x: type(x) == self.lookup_field_type, flask.request.json['ids']))
1263 objects = self._get_objects(ids, **kwargs)
1264 context = {'updating': True, 'objects': objects}
1265 data = self._parse_data(self._get_schema_instance(kwargs, context=context, partial=True),
1266 flask.request)
1267 # just in case an schema allows id as writable.
1268 data.pop('id', None)
1269 data.pop('ids', None)
1270
1271 return self._perform_bulk_update(ids, data, **kwargs), 200
1272
1273 def _bulk_update_query(self, ids, **kwargs):
1274 # It IS better to as is but warn of ON CASCADE
1275 return self.model_class.query.filter(self.model_class.id.in_(ids))
1276
1277 def _pre_bulk_update(self, data, **kwargs):
1278 return {}
1279
1280 def _post_bulk_update(self, ids, extracted_data, **kwargs):
1281 pass
1282
1283 def _perform_bulk_update(self, ids, data, workspace_name=None, **kwargs):
1284 try:
1285 post_bulk_update_data = self._pre_bulk_update(data, **kwargs)
1286 if (len(data) > 0 or len(post_bulk_update_data) > 0) and len(ids) > 0:
1287 queryset = self._bulk_update_query(ids, workspace_name=workspace_name, **kwargs)
1288 updated = queryset.update(data, synchronize_session='fetch')
1289 self._post_bulk_update(ids, post_bulk_update_data, workspace_name=workspace_name)
1290 else:
1291 updated = 0
1292 db.session.commit()
1293 response = {'updated': updated}
1294 return flask.jsonify(response)
1295 except ValueError as e:
1296 db.session.rollback()
1297 flask.abort(400, ValidationError(
1298 {
1299 'message': str(e),
1300 }
1301 ))
1302 except sqlalchemy.exc.IntegrityError as ex:
1303 if not is_unique_constraint_violation(ex):
1304 raise
1305 db.session.rollback()
1306 workspace = None
1307 if workspace_name:
1308 workspace = db.session.query(Workspace).filter_by(name=workspace_name).first()
1309 conflict_obj = get_conflict_object(db.session, self.model_class(), data, workspace)
1310 if conflict_obj is not None:
1311 flask.abort(409, ValidationError(
1312 {
1313 'message': 'Existing value',
1314 'object': self._get_schema_class()().dump(
1315 conflict_obj),
1316 }
1317 ))
1318 elif len(ids) >= 2:
1319 flask.abort(409, ValidationError(
1320 {
1321 'message': 'Updating more than one object with unique data',
1322 'data': data
1323 }
1324 ))
1325 else:
1326 raise
12251327
12261328
12271329 class UpdateWorkspacedMixin(UpdateMixin, CommandMixin):
13131415 return super().patch(object_id, workspace_name=workspace_name)
13141416
13151417
1418 class BulkUpdateWorkspacedMixin(BulkUpdateMixin):
1419
1420 @route('', methods=['PATCH'])
1421 def bulk_update(self, workspace_name, **kwargs):
1422 """
1423 ---
1424 tags: [{tag_name}]
1425 summary: "Delete a group of {class_model} by ids."
1426 responses:
1427 204:
1428 description: Ok
1429 """
1430 return super().bulk_update(workspace_name=workspace_name)
1431
1432 def _bulk_update_query(self, ids, **kwargs):
1433 workspace = self._get_workspace(kwargs["workspace_name"])
1434 return super()._bulk_update_query(ids).filter(self.model_class.workspace_id == workspace.id)
1435
1436
13161437 class DeleteMixin:
13171438 """Add DELETE /<id>/ route"""
13181439
13381459 def _perform_delete(self, obj, workspace_name=None):
13391460 db.session.delete(obj)
13401461 db.session.commit()
1462
1463
1464 class BulkDeleteMixin:
1465 # These mixin should be merged with DeleteMixin after v2 is removed
1466
1467 @route('', methods=['DELETE'])
1468 def bulk_delete(self, **kwargs):
1469 """
1470 ---
1471 tags: [{tag_name}]
1472 summary: "Delete a group of {class_model} by ids."
1473 responses:
1474 204:
1475 description: Ok
1476 """
1477 # TODO BULK_DELETE_SCHEMA
1478 if not flask.request.json or 'ids' not in flask.request.json:
1479 flask.abort(400)
1480 # objs = self._get_objects(flask.request.json['ids'], **kwargs)
1481 # self._perform_bulk_delete(objs, **kwargs)
1482 ids = list(filter(lambda x: type(x) == self.lookup_field_type, flask.request.json['ids']))
1483 return self._perform_bulk_delete(ids, **kwargs), 200
1484
1485 def _bulk_delete_query(self, ids, **kwargs):
1486 # It IS better to as is but warn of ON CASCADE
1487 return self.model_class.query.filter(self.model_class.id.in_(ids))
1488
1489 def _perform_bulk_delete(self, ids, **kwargs):
1490 deleted = self._bulk_delete_query(ids, **kwargs).delete(synchronize_session='fetch')
1491 db.session.commit()
1492 response = {'deleted': deleted}
1493 return flask.jsonify(response)
13411494
13421495
13431496 class DeleteWorkspacedMixin(DeleteMixin):
13721525 return super()._perform_delete(obj, workspace_name)
13731526
13741527
1528 class BulkDeleteWorkspacedMixin(BulkDeleteMixin):
1529 # These mixin should be merged with DeleteMixin after v2 is removed
1530
1531 @route('', methods=['DELETE'])
1532 def bulk_delete(self, workspace_name, **kwargs):
1533 """
1534 ---
1535 tags: [{tag_name}]
1536 summary: "Delete a group of {class_model} by ids."
1537 responses:
1538 204:
1539 description: Ok
1540 """
1541 return super().bulk_delete(workspace_name=workspace_name)
1542
1543 def _bulk_delete_query(self, ids, **kwargs):
1544 workspace = self._get_workspace(kwargs.pop("workspace_name"))
1545 return super()._bulk_delete_query(ids).filter(self.model_class.workspace_id == workspace.id)
1546
1547
13751548 class CountWorkspacedMixin:
13761549 """Add GET /<workspace_name>/<route_base>/count/ route
13771550
66 from flask import Blueprint
77 from marshmallow import fields
88
9 from faraday.server.api.base import AutoSchema, ReadWriteWorkspacedView, PaginatedMixin
9 from faraday.server.api.base import (
10 AutoSchema,
11 ReadWriteWorkspacedView,
12 PaginatedMixin
13 )
1014 from faraday.server.models import Command
1115 from faraday.server.schemas import PrimaryKeyRelatedField
1216
210210 except NoResultFound:
211211 flask.abort(404, f"No such workspace: {workspace_name}")
212212
213 def _update_object(self, obj, data, **kwargs):
214 """Perform changes in the selected object
215
216 It modifies the attributes of the SQLAlchemy model to match
217 the data passed by the Marshmallow schema.
218
219 It is common to overwrite this method to do something strange
220 with some specific field. Typically the new method should call
221 this one to handle the update of the rest of the fields.
222 """
213 def _get_workspaces_from_data(self, data, **kwargs):
223214 workspace_names = data.pop('workspaces', '')
224215 partial = False if 'partial' not in kwargs else kwargs['partial']
225
226216 if len(workspace_names) == 0 and not partial:
227217 abort(
228218 make_response(
237227 400
238228 )
239229 )
240
241230 workspace_names = [
242231 dict_["name"] for dict_ in workspace_names
243232 ]
244
245 workspaces = list(
233 return list(
246234 self._get_workspace(workspace_name)
247235 for workspace_name in workspace_names
248236 )
237
238 def _update_object(self, obj, data, **kwargs):
239 """Perform changes in the selected object
240
241 It modifies the attributes of the SQLAlchemy model to match
242 the data passed by the Marshmallow schema.
243
244 It is common to overwrite this method to do something strange
245 with some specific field. Typically the new method should call
246 this one to handle the update of the rest of the fields.
247 """
248 workspaces = self._get_workspaces_from_data(data, **kwargs)
249249
250250 super()._update_object(obj, data)
251251 obj.workspaces = workspaces
22 Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/)
33 See the file 'doc/LICENSE' for the license information
44 """
5 from __future__ import print_function
6 from __future__ import absolute_import
75
86 import flask
97
99 from flask_classful import route
1010 from marshmallow import fields, post_load, ValidationError
1111
12 from faraday.server.api.base import AutoSchema, ReadWriteWorkspacedView, PaginatedMixin
12 from faraday.server.api.base import (
13 AutoSchema,
14 ReadWriteWorkspacedView,
15 PaginatedMixin
16 )
1317 from faraday.server.models import Command, Workspace
1418 from faraday.server.schemas import MutableField, PrimaryKeyRelatedField, SelfNestedField, MetadataSchema
1519
1111 ReadWriteWorkspacedView,
1212 InvalidUsage,
1313 CreateWorkspacedMixin,
14 GenericWorkspacedView
14 GenericWorkspacedView,
15 BulkDeleteWorkspacedMixin
1516 )
1617 from faraday.server.models import Comment
1718 comment_api = Blueprint('comment_api', __name__)
5152 return super()._perform_create(data, workspace_name)
5253
5354
54 class CommentView(CommentCreateMixing, ReadWriteWorkspacedView):
55 class CommentView(CommentCreateMixing, ReadWriteWorkspacedView, BulkDeleteWorkspacedMixin):
5556 route_base = 'comment'
5657 model_class = Comment
5758 schema_class = CommentSchema
5859 order_field = 'create_date'
5960
6061
61 class UniqueCommentView(GenericWorkspacedView, CommentCreateMixing):
62 class UniqueCommentView(GenericWorkspacedView,
63 CommentCreateMixing):
6264 """
6365 This view is used by the plugin engine to avoid duplicate comments
6466 when the same plugin and data was ran multiple times.
1010 ReadWriteWorkspacedView,
1111 FilterSetMeta,
1212 FilterAlchemyMixin,
13 InvalidUsage
13
14 InvalidUsage,
15 BulkDeleteWorkspacedMixin,
16 BulkUpdateWorkspacedMixin
1417 )
1518 from faraday.server.models import Credential, Host, Service, Workspace, db
1619 from faraday.server.schemas import MutableField, SelfNestedField, MetadataSchema
108111 operators = (operators.Equal, )
109112
110113
111 class CredentialView(FilterAlchemyMixin, ReadWriteWorkspacedView):
114 class CredentialView(FilterAlchemyMixin,
115 ReadWriteWorkspacedView,
116 BulkDeleteWorkspacedMixin,
117 BulkUpdateWorkspacedMixin):
112118 route_base = 'credential'
113119 model_class = Credential
114120 schema_class = CredentialSchema
66 from faraday.server.models import CustomFieldsSchema
77 from faraday.server.api.base import (
88 AutoSchema,
9 ReadWriteView
9 ReadWriteView,
10 BulkDeleteMixin
1011 )
1112
1213
3536 )
3637
3738
38 class CustomFieldsSchemaView(ReadWriteView):
39 class CustomFieldsSchemaView(ReadWriteView, BulkDeleteMixin):
3940 route_base = 'custom_fields_schema'
4041 model_class = CustomFieldsSchema
4142 schema_class = CustomFieldsSchemaSchema
43
44 def _check_post_only_data(self, data):
45 for read_only_key in ['field_name', 'table_name', 'field_type']:
46 data.pop(read_only_key, None)
47 return data
4248
4349 def _update_object(self, obj, data, **kwargs):
4450 """
4551 Field name must be read only
4652 """
47 for read_only_key in ['field_name', 'table_name', 'field_type']:
48 if read_only_key in data:
49 data.pop(read_only_key)
53 data = self._check_post_only_data(data)
5054 return super()._update_object(obj, data)
5155
5256
2323 AutoSchema,
2424 FilterAlchemyMixin,
2525 FilterSetMeta,
26 FilterWorkspacedMixin
26
27 FilterWorkspacedMixin,
28 BulkDeleteWorkspacedMixin,
29 BulkUpdateWorkspacedMixin
2730 )
2831 from faraday.server.schemas import (
2932 MetadataSchema,
136139 class HostsView(PaginatedMixin,
137140 FilterAlchemyMixin,
138141 ReadWriteWorkspacedView,
139 FilterWorkspacedMixin):
142 FilterWorkspacedMixin,
143 BulkDeleteWorkspacedMixin,
144 BulkUpdateWorkspacedMixin):
140145 route_base = 'hosts'
141146 model_class = Host
142147 order_field = desc(Host.vulnerability_critical_generic_count),\
239244 for host_dict in hosts_reader:
240245 try:
241246 hostnames = parse_hosts(host_dict.pop('hostnames'))
242 other_fields = {'owned': False, 'mac': u'00:00:00:00:00:00', 'default_gateway_ip': u'None'}
247 other_fields = {'owned': False, 'mac': '00:00:00:00:00:00', 'default_gateway_ip': 'None'}
243248 host_dict.update(other_fields)
244249 host = super()._perform_create(host_dict, workspace_name)
245250 host.workspace = workspace
398403 or len(hosts)),
399404 }
400405
401 # ### THIS WAS FROM V2
402 # TODO SCHEMA
403 # @route('bulk_delete/', methods=['DELETE'])
404 # def bulk_delete(self, workspace_name):
405 # """
406 # ---
407 # delete:
408 # tags: ["Bulk", "Host"]
409 # description: Delete hosts in bulk
410 # responses:
411 # 200:
412 # description: Ok
413 # 400:
414 # description: Bad request
415 # 403:
416 # description: Forbidden
417 # tags: ["Bulk", "Host"]
418 # responses:
419 # 200:
420 # description: Ok
421 # """
422 # workspace = self._get_workspace(workspace_name)
423 # json_request = flask.request.get_json()
424 # if not json_request:
425 # flask.abort(400, 'Invalid request. Check the request data or the content type of the request')
426 # hosts_ids = json_request.get('hosts_ids', [])
427 # hosts_ids = [host_id for host_id in hosts_ids if isinstance(host_id, int)]
428 # deleted_hosts = 0
429 # if hosts_ids:
430 # deleted_hosts = Host.query.filter(
431 # Host.id.in_(hosts_ids),
432 # Host.workspace_id == workspace.id).delete(synchronize_session='fetch')
433 # else:
434 # flask.abort(400, "Invalid request")
435 #
436 # db.session.commit()
437 # response = {'deleted_hosts': deleted_hosts}
438 # return flask.jsonify(response)
406 @route('', methods=['DELETE'])
407 def bulk_delete(self, workspace_name, **kwargs):
408 # TODO REVISE ORIGINAL METHOD TO UPDATE NEW METHOD
409 return BulkDeleteWorkspacedMixin.bulk_delete(self, workspace_name, **kwargs)
410
411 bulk_delete.__doc__ = BulkDeleteWorkspacedMixin.bulk_delete.__doc__
412
413 def _pre_bulk_update(self, data, **kwargs):
414 hostnames = data.pop('hostnames', None)
415 ans_data = super()._pre_bulk_update(data, **kwargs)
416 if hostnames is not None:
417 ans_data["hostnames"] = hostnames
418 return ans_data
419
420 def _post_bulk_update(self, ids, extracted_data, **kwargs):
421 if "hostnames" in extracted_data:
422 for obj in self._bulk_update_query(ids, **kwargs).all():
423 obj.set_hostnames(extracted_data["hostnames"])
439424
440425
441426 HostsView.register(host_api)
66 from faraday.server.models import License
77 from faraday.server.api.base import (
88 ReadWriteView,
9 AutoSchema
9 AutoSchema,
1010 )
1111 from faraday.server.schemas import (
1212 StrictDateTimeField,
77 from faraday.server.models import SearchFilter
88 from faraday.server.api.base import (
99 ReadWriteView,
10 AutoSchema
10
11 AutoSchema,
12 BulkDeleteMixin,
13 BulkUpdateMixin
1114 )
1215
1316 searchfilter_api = Blueprint('searchfilter_api', __name__)
2326 'json_query', 'user_query')
2427
2528
26 class SearchFilterView(ReadWriteView):
29 class SearchFilterView(ReadWriteView, BulkDeleteMixin, BulkUpdateMixin):
2730 route_base = 'searchfilter'
2831 model_class = SearchFilter
2932 schema_class = SearchFilterSchema
1010 AutoSchema,
1111 ReadWriteWorkspacedView,
1212 FilterSetMeta,
13 FilterAlchemyMixin
13 FilterAlchemyMixin,
14 BulkDeleteWorkspacedMixin,
15 BulkUpdateWorkspacedMixin
1416 )
1517 from faraday.server.models import Host, Service, Workspace
1618 from faraday.server.schemas import (
7577 # Partial update?
7678 return data
7779
78 if host_id != self.context['object'].parent.id:
79 raise ValidationError('Can\'t change service parent.')
80 if 'object' in self.context:
81 if host_id != self.context['object'].parent.id:
82 raise ValidationError('Can\'t change service parent.')
83 else:
84 if any([host_id != obj.parent.id for obj in self.context['objects']]):
85 raise ValidationError('Can\'t change service parent.')
8086
8187 else:
8288 if not host_id:
109115 operators = (operators.Equal,)
110116
111117
112 class ServiceView(FilterAlchemyMixin, ReadWriteWorkspacedView):
118 class ServiceView(FilterAlchemyMixin, ReadWriteWorkspacedView, BulkDeleteWorkspacedMixin, BulkUpdateWorkspacedMixin):
113119
114120 route_base = 'services'
115121 model_class = Service
00 # Faraday Penetration Test IDE
11 # Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/)
22 # See the file 'doc/LICENSE' for the license information
3 from builtins import str, bytes
43 from io import TextIOWrapper
54
65 import json
2625 FilterSetMeta,
2726 PaginatedMixin,
2827 ReadWriteView,
29 FilterMixin
28 FilterMixin,
29 BulkDeleteMixin,
30 BulkUpdateMixin
3031 )
3132
3233 from faraday.server.schemas import (
134135 class VulnerabilityTemplateView(PaginatedMixin,
135136 FilterAlchemyMixin,
136137 ReadWriteView,
137 FilterMixin):
138 FilterMixin,
139 BulkDeleteMixin,
140 BulkUpdateMixin):
138141 route_base = 'vulnerability_template'
139142 model_class = VulnerabilityTemplate
140143 schema_class = VulnerabilityTemplateSchema
1919 from sqlalchemy.orm import aliased, joinedload, selectin_polymorphic, undefer, noload
2020 from sqlalchemy.orm.exc import NoResultFound
2121 from sqlalchemy import desc, or_, func
22 from sqlalchemy.inspection import inspect
2223 from werkzeug.datastructures import ImmutableMultiDict
2324 from depot.manager import DepotManager
2425
3334 PaginatedMixin,
3435 ReadWriteWorkspacedView,
3536 InvalidUsage,
36 CountMultiWorkspacedMixin
37 CountMultiWorkspacedMixin,
38 BulkDeleteWorkspacedMixin,
39 BulkUpdateWorkspacedMixin
3740 )
3841 from faraday.server.fields import FaradayUploadedFile
3942 from faraday.server.models import (
198201 for file_obj in obj.evidence:
199202 try:
200203 res[file_obj.filename] = EvidenceSchema().dump(file_obj)
201 except IOError:
204 except OSError:
202205 logger.warning("File not found. Did you move your server?")
203206
204207 return res
470473 class VulnerabilityView(PaginatedMixin,
471474 FilterAlchemyMixin,
472475 ReadWriteWorkspacedView,
473 CountMultiWorkspacedMixin):
476 CountMultiWorkspacedMixin,
477 BulkDeleteWorkspacedMixin,
478 BulkUpdateWorkspacedMixin):
474479 route_base = 'vulns'
475480 filterset_class = VulnerabilityFilterSet
476481 sort_model_class = VulnerabilityWeb # It has all the fields
10551060 as_attachment=True,
10561061 cache_timeout=-1)
10571062
1058 @route('bulk_delete/', methods=['DELETE'])
1059 def bulk_delete(self, workspace_name):
1060 """
1061 ---
1062 delete:
1063 tags: ["Bulk", "Vulnerability"]
1064 description: Delete vulnerabilities in bulk
1065 responses:
1066 200:
1067 description: Ok
1068 400:
1069 description: Bad request
1070 403:
1071 description: Forbidden
1072 tags: ["Bulk", "Vulnerability"]
1073 responses:
1074 200:
1075 description: Ok
1076 """
1077 workspace = self._get_workspace(workspace_name)
1078 json_quest = request.get_json()
1079 vulnerability_ids = json_quest.get('vulnerability_ids', [])
1080 vulnerability_severities = json_quest.get('severities', [])
1081 deleted_vulns = 0
1082 vulns = []
1083 if vulnerability_ids:
1084 logger.info("Delete Vuln IDs: %s", vulnerability_ids)
1085 vulns = VulnerabilityGeneric.query.filter(VulnerabilityGeneric.id.in_(vulnerability_ids),
1086 VulnerabilityGeneric.workspace_id == workspace.id)
1087 elif vulnerability_severities:
1088 logger.info("Delete Vuln Severities: %s", vulnerability_severities)
1089 vulns = VulnerabilityGeneric.query.filter(VulnerabilityGeneric.severity.in_(vulnerability_severities),
1090 VulnerabilityGeneric.workspace_id == workspace.id)
1091 else:
1092 flask.abort(400, "Invalid Request")
1093 for vuln in vulns:
1094 db.session.delete(vuln)
1095 deleted_vulns += 1
1096 db.session.commit()
1097 response = {'deleted_vulns': deleted_vulns}
1098 return flask.jsonify(response)
1099
11001063 @route('top_users', methods=['GET'])
11011064 def top_users(self, workspace_name):
11021065 """
11291092 response = {'users': users}
11301093 return flask.jsonify(response)
11311094
1095 @route('', methods=['DELETE'])
1096 def bulk_delete(self, workspace_name, **kwargs):
1097 # TODO BULK_DELETE_SCHEMA
1098 if not flask.request.json or 'severities' not in flask.request.json:
1099 return BulkDeleteWorkspacedMixin.bulk_delete(self, workspace_name, **kwargs)
1100 return self._perform_bulk_delete(flask.request.json['severities'], by='severity',
1101 workspace_name=workspace_name, **kwargs), 200
1102 bulk_delete.__doc__ = BulkDeleteWorkspacedMixin.bulk_delete.__doc__
1103
1104 def _bulk_update_query(self, ids, **kwargs):
1105 # It IS better to as is but warn of ON CASCADE
1106 query = self.model_class.query.filter(self.model_class.id.in_(ids))
1107 workspace = self._get_workspace(kwargs.pop("workspace_name"))
1108 return query.filter(self.model_class.workspace_id == workspace.id)
1109
1110 def _bulk_delete_query(self, ids, **kwargs):
1111 # It IS better to as is but warn of ON CASCADE
1112 if kwargs.get("by", "id") != "severity":
1113 query = self.model_class.query.filter(self.model_class.id.in_(ids))
1114 else:
1115 query = self.model_class.query.filter(self.model_class.severity.in_(ids))
1116 workspace = self._get_workspace(kwargs.pop("workspace_name"))
1117 return query.filter(self.model_class.workspace_id == workspace.id)
1118
1119 def _get_model_association_proxy_fields(self):
1120 return [
1121 field.target_collection
1122 for field in inspect(self.model_class).all_orm_descriptors
1123 if field.extension_type.name == "ASSOCIATION_PROXY"
1124 ]
1125
1126 def _pre_bulk_update(self, data, **kwargs):
1127 data.pop('type', '') # It's forbidden to change vuln type!
1128 data.pop('tool', '')
1129 data.pop('service_id', '')
1130 data.pop('host_id', '')
1131 # TODO For now, we don't want to accept multiples attachments; moreover, attachments have its own endpoint
1132 data.pop('_attachments', [])
1133 super()._pre_bulk_update(data, **kwargs)
1134
1135 model_association_proxy_fields = self._get_model_association_proxy_fields()
1136 association_proxy_fields = {}
1137 for key in list(data):
1138 parent = getattr(VulnerabilityWeb, key).parent
1139 field_name = getattr(parent, "target_collection", None)
1140 if field_name and field_name in model_association_proxy_fields:
1141 association_proxy_fields[key] = data.pop(key)
1142 return association_proxy_fields
1143
1144 def _post_bulk_update(self, ids, extracted_data, workspace_name, **kwargs):
1145 if extracted_data:
1146 queryset = self._bulk_update_query(
1147 ids,
1148 workspace_name=workspace_name,
1149 **kwargs)
1150 for obj in queryset.all():
1151 for (key, value) in extracted_data.items():
1152 setattr(obj, key, value)
1153 db.session.add(obj)
1154
11321155
11331156 VulnerabilityView.register(vulns_api)
00 # Faraday Penetration Test IDE
11 # Copyright (C) 2016 Infobyte LLC (http://www.infobytesec.com/)
22 # See the file 'doc/LICENSE' for the license information
3 from datetime import timedelta, date
4
35 import re
4 from builtins import str
56
67 import json
78 import logging
9 from itertools import groupby
810
911 import flask
1012 from flask import Blueprint, abort, make_response, jsonify
1517 )
1618 from sqlalchemy.orm.exc import NoResultFound
1719
18
1920 from faraday.server.models import (db,
2021 Workspace,
2122 _make_vuln_count_property,
2223 Vulnerability,
2324 _make_active_agents_count_property,
2425 count_vulnerability_severities,
25 _last_run_agent_date)
26 _last_run_agent_date,
27 SeveritiesHistogram)
2628 from faraday.server.schemas import (
2729 JSTimestampField,
2830 MutableField,
2931 PrimaryKeyRelatedField,
3032 SelfNestedField,
3133 )
32 from faraday.server.api.base import ReadWriteView, AutoSchema, FilterMixin
34 from faraday.server.api.base import ReadWriteView, AutoSchema, FilterMixin, BulkDeleteMixin
3335
3436 logger = logging.getLogger(__name__)
3537
5456 total_vulns = fields.Integer(dump_only=True, allow_none=False, attribute='vulnerability_total_count')
5557
5658
59 class HistogramSchema(Schema):
60 date = fields.Date(dump_only=True, attribute='date')
61 medium = fields.Integer(dump_only=True, attribute='medium')
62 high = fields.Integer(dump_only=True, attribute='high')
63 critical = fields.Integer(dump_only=True, attribute='critical')
64 confirmed = fields.Integer(dump_only=True, attribute='confirmed')
65
66
5767 class WorkspaceDurationSchema(Schema):
5868 start_date = JSTimestampField(attribute='start_date')
5969 end_date = JSTimestampField(attribute='end_date')
6373 blacklist = ["filter"]
6474 if name in blacklist:
6575 raise ValidationError(f"Not possible to create workspace of name: {name}")
66 if not re.match(r"^[a-z0-9][a-z0-9_$()+-]*$", name):
76 if not re.match(r"^[a-z0-9][a-z0-9_$()+-]{0,250}$", name):
6777 raise ValidationError("The workspace name must validate with the regex "
68 "^[a-z0-9][a-z0-9_$()+-]*$")
78 "^[a-z0-9][a-z0-9_$()+-]{0,250}$")
6979
7080
7181 class WorkspaceSchema(AutoSchema):
8494 update_date = fields.DateTime(attribute='update_date', dump_only=True)
8595 active_agents_count = fields.Integer(dump_only=True)
8696 last_run_agent_date = fields.DateTime(dump_only=True, attribute='last_run_agent_date')
97 histogram = fields.Nested(HistogramSchema(many=True))
8798
8899 class Meta:
89100 model = Workspace
90101 fields = ('_id', 'id', 'customer', 'description', 'active',
91102 'duration', 'name', 'public', 'scope', 'stats',
92103 'create_date', 'update_date', 'readonly',
93 'active_agents_count', 'last_run_agent_date')
104 'active_agents_count', 'last_run_agent_date', 'histogram')
94105
95106 @post_load
96107 def post_load_duration(self, data, **kwargs):
104115 return data
105116
106117
107 class WorkspaceView(ReadWriteView, FilterMixin):
118 def init_date_range(from_day, days):
119 date_list = [{'date': from_day - timedelta(days=x),
120 Vulnerability.SEVERITY_MEDIUM: 0,
121 Vulnerability.SEVERITY_HIGH: 0,
122 Vulnerability.SEVERITY_CRITICAL: 0,
123 'confirmed': 0} for x in range(days)]
124 return date_list
125
126
127 def generate_histogram(from_date, days_before):
128 histogram_dict = dict()
129
130 workspaces_histograms = SeveritiesHistogram.query \
131 .order_by(SeveritiesHistogram.workspace_id.asc(), SeveritiesHistogram.date.asc()).all()
132
133 # group dates by workspace
134 grouped_histograms_by_ws = groupby(workspaces_histograms, lambda x: x.workspace.name)
135
136 ws_histogram = {}
137 for ws_name, dates in grouped_histograms_by_ws:
138 first_date = None
139 ws_histogram[ws_name] = {}
140 # Convert to dict
141 for d in dates:
142 if first_date is None:
143 first_date = d.date
144 ws_histogram[ws_name][d.date] = {Vulnerability.SEVERITY_MEDIUM: d.medium,
145 Vulnerability.SEVERITY_HIGH: d.high,
146 Vulnerability.SEVERITY_CRITICAL: d.critical,
147 'confirmed': d.confirmed}
148
149 # fix histogram gaps
150 if (date.today() - first_date).days < days_before:
151 # move first_date to diff between first day and days required
152 first_date = first_date - timedelta(days=(days_before - (date.today() - first_date).days))
153 histogram_dict[ws_name] = [{'date': first_date + timedelta(days=x),
154 Vulnerability.SEVERITY_MEDIUM: 0,
155 Vulnerability.SEVERITY_HIGH: 0,
156 Vulnerability.SEVERITY_CRITICAL: 0,
157 'confirmed': 0}
158 for x in range((date.today() - first_date).days + 1)]
159
160 # merge counters with days required
161 confirmed = high = medium = critical = 0
162 for current_workspace_histogram_counters in histogram_dict[ws_name]:
163 current_date = current_workspace_histogram_counters['date']
164 if current_date in ws_histogram[ws_name]:
165 medium += ws_histogram[ws_name][current_date][Vulnerability.SEVERITY_MEDIUM]
166 high += ws_histogram[ws_name][current_date][Vulnerability.SEVERITY_HIGH]
167 critical += ws_histogram[ws_name][current_date][Vulnerability.SEVERITY_CRITICAL]
168 confirmed += ws_histogram[ws_name][current_date]['confirmed']
169 current_workspace_histogram_counters[Vulnerability.SEVERITY_MEDIUM] = medium
170 current_workspace_histogram_counters[Vulnerability.SEVERITY_HIGH] = high
171 current_workspace_histogram_counters[Vulnerability.SEVERITY_CRITICAL] = critical
172 current_workspace_histogram_counters['confirmed'] = confirmed
173 histogram_dict[ws_name] = histogram_dict[ws_name][-days_before:]
174
175 return histogram_dict
176
177
178 class WorkspaceView(ReadWriteView, FilterMixin, BulkDeleteMixin):
108179 route_base = 'ws'
109180 lookup_field = 'name'
110181 lookup_field_type = str
129200 200:
130201 description: Ok
131202 """
203 histogram = flask.request.args.get('histogram', type=lambda v: v.lower() == 'true')
204
205 if histogram:
206 today = date.today()
207
208 histogram_days = flask.request.args.get('histogram_days',
209 type=lambda x: int(x)
210 if x.isnumeric() and int(x) > 0
211 else SeveritiesHistogram.DEFAULT_DAYS_BEFORE,
212 default=SeveritiesHistogram.DEFAULT_DAYS_BEFORE
213 )
214 histogram_dict = generate_histogram(today, histogram_days)
215
132216 query = self._get_base_query()
217
133218 objects = []
134219 for workspace_stat in query:
135220 workspace_stat_dict = dict(workspace_stat)
142227 workspace_stat_dict['scope_raw'] = workspace_stat_dict['scope_raw'].split(',')
143228 for scope in workspace_stat_dict['scope_raw']:
144229 workspace_stat_dict['scope'].append({'name': scope})
230
231 if histogram:
232 if workspace_stat_dict['name'] in histogram_dict:
233 workspace_stat_dict['histogram'] = histogram_dict[workspace_stat_dict['name']]
234 else:
235 workspace_stat_dict['histogram'] = init_date_range(today, histogram_days)
236
145237 objects.append(workspace_stat_dict)
146238 return self._envelope_list(self._dump(objects, kwargs, many=True))
147239
361453 db.session.commit()
362454 return self._get_object(workspace_id).readonly
363455
456 def _bulk_delete_query(self, ids, **kwargs):
457 # It IS better to as is but warn of ON CASCADE
458 return self.model_class.query.filter(self.model_class.name.in_(ids))
459
364460
365461 WorkspaceView.register(workspace_api)
101101 # Custom reset password
102102 from faraday.server.api.modules.auth import auth # pylint:disable=import-outside-toplevel
103103 from faraday.server.websockets import websockets # pylint:disable=import-outside-toplevel
104 from faraday.server.api.modules.settings_reports import reports_settings_api # pylint:disable=import-outside-toplevel
104 from faraday.server.api.modules.settings_reports import \
105 reports_settings_api # pylint:disable=import-outside-toplevel
105106 from faraday.server.api.modules.settings_dashboard import \
106107 dashboard_settings_api # pylint:disable=import-outside-toplevel
107108
268269 user_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
269270 user_logout_at = datetime.datetime.utcnow()
270271 audit_logger.info(f"User [{user.username}] logged out from IP [{user_ip}] at [{user_logout_at}]")
272 logger.info(f"User [{user.username}] logged out from IP [{user_ip}] at [{user_logout_at}]")
271273
272274
273275 def user_logged_in_succesfull(app, user):
288290 user_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
289291 user_login_at = datetime.datetime.utcnow()
290292 audit_logger.info(f"User [{user.username}] logged in from IP [{user_ip}] at [{user_login_at}]")
293 logger.info(f"User [{user.username}] logged in from IP [{user_ip}] at [{user_login_at}]")
291294
292295
293296 def uia_username_mapper(identity):
484487 # want to skip the LoginForm validate logic
485488 if not super(LoginForm, self).validate():
486489 audit_logger.warning(f"Invalid Login - User [{self.email.data}] from IP [{user_ip}] at [{time_now}]")
490 logger.warning(f"Invalid Login - User [{self.email.data}] from IP [{user_ip}] at [{time_now}]")
487491 return False
488492 self.email.data = remove_null_caracters(self.email.data)
489493
492496 if self.user is None:
493497 audit_logger.warning(f"Invalid Login - User [{self.email.data}] from IP [{user_ip}] at [{time_now}] - "
494498 f"Reason: [Invalid Username]")
499 logger.warning(f"Invalid Login - User [{self.email.data}] from IP [{user_ip}] at [{time_now}] - "
500 f"Reason: [Invalid Username]")
495501 self.email.errors.append(get_message('USER_DOES_NOT_EXIST')[0])
496502 return False
497503
499505 if not self.user.password:
500506 audit_logger.warning(f"Invalid Login - User [{self.email.data}] from IP [{user_ip}] at [{time_now}] - "
501507 f"Reason: [Invalid Password]")
508 logger.warning(f"Invalid Login - User [{self.email.data}] from IP [{user_ip}] at [{time_now}] - "
509 f"Reason: [Invalid Password]")
502510 self.email.errors.append(get_message('USER_DOES_NOT_EXIST')[0])
503511 return False
504512 self.password.data = remove_null_caracters(self.password.data)
505513 if not verify_and_update_password(self.password.data, self.user):
506514 audit_logger.warning(f"Invalid Login - User [{self.email.data}] from IP [{user_ip}] at [{time_now}] - "
507515 f"Reason: [Invalid Password]")
516 logger.warning(f"Invalid Login - User [{self.email.data}] from IP [{user_ip}] at [{time_now}] - "
517 f"Reason: [Invalid Password]")
508518 self.email.errors.append(get_message('USER_DOES_NOT_EXIST')[0])
509519 return False
510520 # if requires_confirmation(self.user):
513523 if not self.user.is_active:
514524 audit_logger.warning(f"Invalid Login - User [{self.email.data}] from IP [{user_ip}] at [{time_now}] - "
515525 f"Reason: [Disabled Account]")
526 logger.warning(f"Invalid Login - User [{self.email.data}] from IP [{user_ip}] at [{time_now}] - "
527 f"Reason: [Disabled Account]")
516528 self.email.errors.append(get_message('DISABLED_ACCOUNT')[0])
517529 return False
518530 return True
2222 print(f"Username {current_username} changed to {new_username}")
2323 else:
2424 print("Username not changed.")
25
26
27 # I'm Py3
8989 custom_field_data.field_display_name = field_display_name
9090 custom_field_data.field_type = field_type
9191 db.session.commit()
92 # I'm Py3
6969 )
7070 graph.write_png('uml_schema.png') # write out the file
7171 print("Graph written to fle uml_schema.png")
72 # I'm Py3
0 from __future__ import absolute_import
1
20 import csv
31 import tempfile
42
33 See the file 'doc/LICENSE' for the license information
44
55 """
6 from builtins import input
76
87 import getpass
98 import string
336335 print(f'{Fore.BLUE}MAC OS detected{Fore.WHITE}')
337336 postgres_command = ['psql', 'postgres']
338337 password = self.generate_random_pw(25)
339 command = postgres_command + ['-c', 'CREATE ROLE {0} WITH LOGIN PASSWORD \'{1}\';'.format(username, password)]
338 command = postgres_command + ['-c', f'CREATE ROLE {username} WITH LOGIN PASSWORD \'{password}\';']
340339 p = Popen(command, stderr=psql_log_file, stdout=psql_log_file) # nosec
341340 p.wait()
342341 psql_log_file.seek(0)
2626 if name:
2727 name = name.lower()
2828 available_settings = get_all_settings()
29 if action in ('show', 'update'):
29 if action in ('show', 'update', 'clear'):
3030 if not name:
3131 click.secho(f"You must indicate a settings name to {action}", fg="red")
3232 sys.exit(1)
7979 click.secho("Updated!!", fg='green')
8080 else:
8181 click.secho("No changes where made to the settings", fg="green")
82
82 elif action == "clear":
83 click.secho(f"Clear settings for: {name}", fg="green")
84 settings.delete_configuration()
8385 else:
8486 click.secho("Available settings:", fg="green")
8587 for i in available_settings:
1414 confirm = click.prompt('Confirm [Y/n]', type=bool)
1515 if confirm:
1616 version = sys.version_info
17 static_path = f"/opt/faraday/lib/python{version.major}.{version.minor}/site-packages/faraday/server/www/"
17 static_path = f"/opt/faraday/lib/python{version.major}.{version.minor}/site-packages/faraday/server/www"
1818 templates_path = Path(__file__).parent / 'templates'
1919 file_loader = FileSystemLoader(templates_path)
2020 env = Environment(loader=file_loader, autoescape=True)
88 ssl_certificate {{ ssl_certificate }};
99 ssl_certificate_key {{ ssl_key }};
1010
11 root {{ static_path }};
12 index index.html index.htm;
13
1114 location /{% if multitenant_url %}{{ multitenant_url }}/{% endif %} {
12 alias {{ static_path }};
15 try_files $uri $uri/ /index.html;
1316 }
1417
1518 location {% if multitenant_url %}/{{ multitenant_url }}{% endif %}/_api/ {
66 import sys
77 import logging
88 import inspect
9 from datetime import date
910 from queue import Queue
1011
1112 from sqlalchemy import event
13 from sqlalchemy.dialects import postgresql
14 from sqlalchemy.orm import Query
15 from sqlalchemy.orm.attributes import get_history
1216
1317 from faraday.server.models import (
1418 Host,
1620 TagObject,
1721 Comment,
1822 File,
23 SeveritiesHistogram,
24 Vulnerability,
25 VulnerabilityWeb,
26 VulnerabilityGeneric,
1927 )
2028 from faraday.server.models import db
2129
97105 "This should never happen!!!"
98106
99107
108 def _create_or_update_histogram(connection, workspace_id=None, medium=0, high=0, critical=0, confirmed=0):
109 if workspace_id is None:
110 logger.error("Workspace with None value. Histogram could not be updated")
111 return
112 ws_id = SeveritiesHistogram.query.with_entities('id').filter(
113 SeveritiesHistogram.date == date.today(),
114 SeveritiesHistogram.workspace_id == workspace_id).first()
115 if ws_id is None:
116 connection.execute(
117 f"INSERT " # nosec
118 f"INTO severities_histogram (workspace_id, medium, high, critical, date, confirmed) "
119 f"VALUES ({workspace_id}, {medium}, {high}, {critical}, '{date.today()}', {confirmed})")
120 else:
121 connection.execute(
122 f"UPDATE severities_histogram " # nosec
123 f"SET medium = medium + {medium}, "
124 f"high = high + {high}, "
125 f"critical = critical + {critical}, "
126 f"confirmed = confirmed + {confirmed} "
127 f"WHERE id = {ws_id[0]}")
128
129
130 def _dicrease_severities_histogram(instance_severity, medium=0, high=0, critical=0):
131 medium = -1 if instance_severity == Vulnerability.SEVERITY_MEDIUM else medium
132 high = -1 if instance_severity == Vulnerability.SEVERITY_HIGH else high
133 critical = -1 if instance_severity == Vulnerability.SEVERITY_CRITICAL else critical
134
135 return medium, high, critical
136
137
138 def _increase_severities_histogram(instance_severity, medium=0, high=0, critical=0):
139 medium = 1 if instance_severity == Vulnerability.SEVERITY_MEDIUM else medium
140 high = 1 if instance_severity == Vulnerability.SEVERITY_HIGH else high
141 critical = 1 if instance_severity == Vulnerability.SEVERITY_CRITICAL else critical
142
143 return medium, high, critical
144
145
146 def alter_histogram_on_insert(mapper, connection, instance):
147 if instance.severity in SeveritiesHistogram.SEVERITIES_ALLOWED:
148 medium, high, critical = _increase_severities_histogram(instance.severity)
149 confirmed = 1 if instance.confirmed else 0
150
151 _create_or_update_histogram(connection,
152 instance.workspace_id,
153 medium=medium,
154 high=high,
155 critical=critical,
156 confirmed=confirmed)
157
158
159 def alter_histogram_on_update(mapper, connection, instance):
160 alter_histogram_on_update_general(connection,
161 instance.workspace_id,
162 status_history=get_history(instance, 'status'),
163 confirmed_history=get_history(instance, 'confirmed'),
164 severity_history=get_history(instance, 'severity'))
165
166
167 def alter_histogram_on_update_general(connection, workspace_id, status_history=None,
168 confirmed_history=None, severity_history=None):
169
170 if not status_history or not confirmed_history or not severity_history:
171 logger.error("Not all history fields provided")
172 return
173
174 if len(confirmed_history.unchanged) > 0:
175 confirmed_counter = 0
176 confirmed_counter_on_close = -1 if confirmed_history.unchanged[0] is True else 0
177 confirmed_counter_on_reopen = 1 if confirmed_history.unchanged[0] is True else 0
178 else:
179 if not confirmed_history.deleted or not confirmed_history.added:
180 logger.error("Confirmed history deleted or added is None. Could not update confirmed value.")
181 return
182 if confirmed_history.deleted[0] is True:
183 confirmed_counter = -1
184 confirmed_counter_on_close = confirmed_counter
185 confirmed_counter_on_reopen = 0
186 else:
187 confirmed_counter = 1
188 confirmed_counter_on_close = 0
189 confirmed_counter_on_reopen = confirmed_counter
190
191 if len(status_history.unchanged) > 0:
192 if len(severity_history.unchanged) > 0:
193 if confirmed_counter != 0 and status_history.unchanged[0] in [Vulnerability.STATUS_OPEN, Vulnerability.STATUS_RE_OPENED]:
194 _create_or_update_histogram(connection, workspace_id, confirmed=confirmed_counter)
195 return
196 medium = high = critical = 0
197 if not severity_history.deleted or not severity_history.added:
198 if confirmed_counter != 0 and status_history.unchanged[0] in [Vulnerability.STATUS_OPEN, Vulnerability.STATUS_RE_OPENED]:
199 _create_or_update_histogram(connection, workspace_id, confirmed=confirmed_counter)
200 logger.error("Severity history deleted or added is None. Could not update severity histogram.")
201 return
202
203 if severity_history.deleted[0] in SeveritiesHistogram.SEVERITIES_ALLOWED:
204 medium, high, critical = _dicrease_severities_histogram(severity_history.deleted[0])
205
206 if severity_history.added[0] in SeveritiesHistogram.SEVERITIES_ALLOWED:
207 medium, high, critical = _increase_severities_histogram(severity_history.added[0],
208 medium=medium,
209 high=high,
210 critical=critical)
211 _create_or_update_histogram(connection,
212 workspace_id,
213 medium=medium,
214 high=high,
215 critical=critical,
216 confirmed=confirmed_counter)
217
218 elif status_history.added[0] in [Vulnerability.STATUS_CLOSED, Vulnerability.STATUS_RISK_ACCEPTED]\
219 and status_history.deleted[0] in [Vulnerability.STATUS_OPEN, Vulnerability.STATUS_RE_OPENED]:
220 if len(severity_history.unchanged) > 0:
221 severity = severity_history.unchanged[0]
222 if len(severity_history.deleted) > 0:
223 severity = severity_history.deleted[0]
224 if severity in SeveritiesHistogram.SEVERITIES_ALLOWED:
225 medium, high, critical = _dicrease_severities_histogram(severity)
226 _create_or_update_histogram(connection, workspace_id, medium=medium, high=high,
227 critical=critical, confirmed=confirmed_counter_on_close)
228 elif status_history.added[0] in [Vulnerability.STATUS_OPEN, Vulnerability.STATUS_RE_OPENED] \
229 and status_history.deleted[0] in [Vulnerability.STATUS_CLOSED, Vulnerability.STATUS_RISK_ACCEPTED]:
230 if len(severity_history.unchanged) > 0:
231 severity = severity_history.unchanged[0]
232 if len(severity_history.added) > 0:
233 severity = severity_history.added[0]
234 if severity in SeveritiesHistogram.SEVERITIES_ALLOWED:
235 medium, high, critical = _increase_severities_histogram(severity)
236 _create_or_update_histogram(connection, workspace_id, medium=medium, high=high,
237 critical=critical, confirmed=confirmed_counter_on_reopen)
238 elif confirmed_counter != 0:
239 _create_or_update_histogram(connection, workspace_id, confirmed=confirmed_counter)
240
241
242 def alter_histogram_on_delete(mapper, connection, instance):
243 if instance.status in [Vulnerability.STATUS_OPEN, Vulnerability.STATUS_RE_OPENED]:
244 confirmed = -1 if instance.confirmed is True else 0
245 if instance.severity in SeveritiesHistogram.SEVERITIES_ALLOWED:
246 medium, high, critical = _dicrease_severities_histogram(instance.severity)
247 _create_or_update_histogram(connection, instance.workspace_id,
248 medium=medium,
249 high=high,
250 critical=critical,
251 confirmed=confirmed)
252
253
254 def alter_histogram_on_before_compile_delete(query, delete_context):
255 for desc in query.column_descriptions:
256 if desc['type'] is Vulnerability or \
257 desc['type'] is VulnerabilityGeneric or\
258 desc['type'] is VulnerabilityWeb:
259 instances = query.all()
260 for instance in instances:
261 if instance.status in [Vulnerability.STATUS_OPEN, Vulnerability.STATUS_RE_OPENED]:
262 if instance.severity in SeveritiesHistogram.SEVERITIES_ALLOWED:
263 medium, high, critical = _dicrease_severities_histogram(instance.severity)
264 _create_or_update_histogram(delete_context.session,
265 instance.workspace_id,
266 medium=medium,
267 high=high,
268 critical=critical,
269 confirmed=-1 if instance.confirmed is True else 0)
270
271
272 def get_history_from_context_values(context_values, field, old_value):
273 field_history = type('history_dummy_class', (object,), {'added': [], 'unchanged': [old_value], 'deleted': []})()
274 if field in context_values:
275 if context_values[field] != old_value:
276 field_history.deleted.append(old_value)
277 field_history.added.append(context_values[field])
278 field_history.unchanged.pop()
279 return field_history
280
281
282 def alter_histogram_on_before_compile_update(query, update_context):
283 for desc in query.column_descriptions:
284 if desc['type'] is Vulnerability or \
285 desc['type'] is VulnerabilityGeneric or\
286 desc['type'] is VulnerabilityWeb:
287 ids = [x[1] for x in filter(lambda x: x[0].startswith("id_"),
288 query.statement.compile(dialect=postgresql.dialect()).params.items())]
289 if ids:
290 # this can arise some issues with counters when other filters were applied to query but...
291 instances = update_context.session.query(VulnerabilityGeneric).filter(
292 VulnerabilityGeneric.id.in_(ids)).all()
293 else:
294 instances = query.all()
295
296 for instance in instances:
297 status_history = get_history_from_context_values(update_context.values, 'status', instance.status)
298 severity_history = get_history_from_context_values(update_context.values, 'severity', instance.severity)
299 confirmed_history = get_history_from_context_values(update_context.values, 'confirmed',
300 instance.confirmed)
301
302 alter_histogram_on_update_general(update_context.session,
303 instance.workspace_id,
304 status_history=status_history,
305 confirmed_history=confirmed_history,
306 severity_history=severity_history)
307
308
100309 # register the workspace verification for all objs that has workspace_id
101310 for name, obj in inspect.getmembers(sys.modules['faraday.server.models']):
102311 if inspect.isclass(obj) and getattr(obj, 'workspace_id', None):
115324 # Update object bindings
116325 event.listen(Host, 'after_update', update_object_event)
117326 event.listen(Service, 'after_update', update_object_event)
327
328 # Severities Histogram
329 event.listen(VulnerabilityGeneric, "before_insert", alter_histogram_on_insert, propagate=True)
330 event.listen(VulnerabilityGeneric, "before_update", alter_histogram_on_update, propagate=True)
331 event.listen(VulnerabilityGeneric, "after_delete", alter_histogram_on_delete, propagate=True)
332 event.listen(Query, "before_compile_delete", alter_histogram_on_before_compile_delete)
333 event.listen(Query, "before_compile_update", alter_histogram_on_before_compile_update)
33 See the file 'doc/LICENSE' for the license information
44
55 """
6 from builtins import str
76
87 import json
98 import imghdr
124123 if value is not None:
125124 value = json.loads(value)
126125 return value
127 # I'm Py3
22 # See the file 'doc/LICENSE' for the license information
33 import json
44 import logging
5 import math
56 import operator
7 import re
68 import string
7 from datetime import datetime, timedelta
9 from datetime import datetime, timedelta, date
810 from functools import partial
911 from random import SystemRandom
12 from typing import Callable
1013
1114 from sqlalchemy import (
1215 Boolean,
2326 event,
2427 Table,
2528 literal,
29 Date,
2630 )
2731 from sqlalchemy.exc import IntegrityError
2832 from sqlalchemy.orm import relationship
449453 id = Column(Integer, primary_key=True)
450454 name = NonBlankColumn(Text)
451455
452 host_id = Column(Integer, ForeignKey('host.id'), index=True, nullable=False)
456 host_id = Column(Integer, ForeignKey('host.id', ondelete='CASCADE'), index=True, nullable=False)
453457 host = relationship('Host', backref=backref("hostnames", cascade="all, delete-orphan"))
454458
455459 # 1 workspace <--> N hostnames
494498 'difficult',
495499 'infeasible'
496500 ]
501
502 SEVERITY_UNCLASSIFIED = 'unclassified'
503 SEVERITY_INFORMATIONAL = 'informational'
504 SEVERITY_LOW = 'low'
505 SEVERITY_MEDIUM = 'medium'
506 SEVERITY_HIGH = 'high'
507 SEVERITY_CRITICAL = 'critical'
508
497509 SEVERITIES = [
498 'unclassified',
499 'informational',
500 'low',
501 'medium',
502 'high',
503 'critical',
510 SEVERITY_UNCLASSIFIED,
511 SEVERITY_INFORMATIONAL,
512 SEVERITY_LOW,
513 SEVERITY_MEDIUM,
514 SEVERITY_HIGH,
515 SEVERITY_CRITICAL,
504516 ]
505517
506518 __abstract__ = True
531543 @property
532544 def parent(self):
533545 raise NotImplementedError('ABC property called')
546
547
548 class SeveritiesHistogram(db.Model):
549 __tablename__ = "severities_histogram"
550
551 SEVERITIES_ALLOWED = [VulnerabilityABC.SEVERITY_MEDIUM,
552 VulnerabilityABC.SEVERITY_HIGH,
553 VulnerabilityABC.SEVERITY_CRITICAL]
554
555 DEFAULT_DAYS_BEFORE = 20
556
557 id = Column(Integer, primary_key=True)
558 workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False)
559 workspace = relationship(
560 'Workspace',
561 foreign_keys=[workspace_id],
562 backref=backref('severities_histogram', cascade="all, delete-orphan")
563 )
564 date = Column(Date, default=date.today(), nullable=False)
565 medium = Column(Integer, nullable=False)
566 high = Column(Integer, nullable=False)
567 critical = Column(Integer, nullable=False)
568 confirmed = Column(Integer, nullable=False)
569
570 # This method is required by event :_(
571 @property
572 def parent(self):
573 return
534574
535575
536576 class CustomAssociationSet(_AssociationSet):
616656 return creator
617657
618658
619 def _build_associationproxy_creator_non_workspaced(model_class_name):
659 def _build_associationproxy_creator_non_workspaced(model_class_name, preprocess_value_func: Callable = None):
620660 def creator(name, vulnerability):
621661 """Get or create a reference/policyviolation/CVE with the
622662 corresponding name. This is not workspace aware"""
624664 # Ugly hack to avoid the fact that Reference is defined after
625665 # Vulnerability
626666 model_class = globals()[model_class_name]
667
668 if preprocess_value_func:
669 name = preprocess_value_func(name)
670
627671 child = model_class.query.filter(
628672 getattr(model_class, 'name') == name,
629673 ).first()
683727 object_type = Column(Enum(*OBJECT_TYPES, name='object_types'), nullable=False)
684728
685729 command = relationship('Command', backref='command_objects')
686 command_id = Column(Integer, ForeignKey('command.id'), index=True)
730 command_id = Column(Integer, ForeignKey('command.id', ondelete='SET NULL'), index=True)
687731
688732 # 1 workspace <--> N command_objects
689733 # 1 to N (the FK is placed in the child) and bidirectional (backref)
792836
793837 # 1 workspace <--> N commands
794838 # 1 to N (the FK is placed in the child) and bidirectional (backref)
795 workspace_id = Column(Integer, ForeignKey('workspace.id'), index=True, nullable=False)
839 workspace_id = Column(Integer, ForeignKey('workspace.id', ondelete="CASCADE"), index=True, nullable=False)
796840 workspace = relationship(
797841 'Workspace',
798842 foreign_keys=[workspace_id],
10321076 raise ValueError("Invalid cve format. Should be CVE-YEAR-NUMBERID.")
10331077
10341078
1079 class CVSS2GeneralConfig:
1080 VERSION = '2'
1081 PATTERN = 'AV:(?P<access_vector>[LAN])' \
1082 '/AC:(?P<access_complexity>[HML])' \
1083 '/Au:(?P<authentication>[MSN])' \
1084 '/C:(?P<confidentiality>[NPC])' \
1085 '/I:(?P<integrity>[NPC])' \
1086 '/A:(?P<availability>[NPC])'
1087
1088 # CVSSV2 ENUMS
1089 ACCESS_VECTOR_TYPES = ['L', 'N', 'A']
1090 ACCESS_COMPLEXITY_TYPES = ['L', 'M', 'H']
1091 AUTHENTICATION_TYPES = ['N', 'S', 'M']
1092 IMPACT_TYPES_V2 = ['N', 'P', 'C']
1093
1094 # CVSSV2 SCORE
1095 ACCESS_VECTOR_SCORE = {'L': 0.395, 'A': 0.646, 'N': 1.0}
1096 ACCESS_COMPLEXITY_SCORE = {'L': 0.71, 'M': 0.61, 'H': 0.35}
1097 AUTHENTICATION_SCORE = {'N': 0.704, 'S': 0.56, 'M': 0.45}
1098 IMPACT_SCORES_V2 = {'N': 0.0, 'P': 0.275, 'C': 0.660}
1099
1100
1101 class CVSS3GeneralConfig:
1102 VERSION = '3'
1103 PATTERN = 'AV:(?P<attack_vector>[LANP])' \
1104 '/AC:(?P<attack_complexity>[HL])' \
1105 '/PR:(?P<privileges_required>[NLH])' \
1106 '/UI:(?P<user_interaction>[NR])' \
1107 '/S:(?P<scope>[UC])' \
1108 '/C:(?P<confidentiality>[NLH])' \
1109 '/I:(?P<integrity>[NLH])' \
1110 '/A:(?P<availability>[NLH])'
1111
1112 CHANGED = 'C'
1113 UNCHANGED = 'U'
1114
1115 # CVSSV3 ENUMS
1116 ATTACK_VECTOR_TYPES = ['N', 'A', 'L', 'P']
1117 ATTACK_COMPLEXITY_TYPES = ['L', 'H']
1118 PRIVILEGES_REQUIRED_TYPES = ['N', 'L', 'H']
1119 USER_INTERACTION_TYPES = ['N', 'R']
1120 SCOPE_TYPES = [UNCHANGED, CHANGED]
1121 IMPACT_TYPES_V3 = ['N', 'L', 'H']
1122
1123 # CVSSV3 SCORE
1124 ATTACK_VECTOR_SCORES = {'N': 0.85, 'A': 0.62, 'L': 0.55, 'P': 0.2}
1125 ATTACK_COMPLEXITY_SCORES = {'L': 0.77, 'H': 0.44}
1126 PRIVILEGES_REQUIRED_SCORES = {'U': {'N': 0.85, 'L': 0.62, 'H': 0.27},
1127 'C': {'N': 0.85, 'L': 0.68, 'H': 0.5}}
1128 USER_INTERACTION_SCORES = {'N': 0.85, 'R': 0.62}
1129 SCOPE_SCORES = {'U': 6.42, 'C': 7.52}
1130 IMPACT_SCORES_V3 = {'N': 0.0, 'L': 0.22, 'H': 0.56}
1131
1132
1133 class CVSSBase(db.Model):
1134 __tablename__ = "cvss_base"
1135 id = Column(Integer, primary_key=True)
1136 version = Column(String(8), nullable=False)
1137 _vector_string = Column('vector_string', String(64))
1138 _base_score = Column('base_score', Float)
1139 _fixed_base_score = Column('fixed_base_score', Float)
1140
1141 type = Column(String(24))
1142
1143 __mapper_args__ = {
1144 'polymorphic_on': type,
1145 'polymorphic_identity': 'base'
1146 }
1147
1148 @hybrid_property
1149 def vector_string(self):
1150 return self._vector_string
1151
1152 @vector_string.setter
1153 def vector_string(self, vector_string):
1154 self.assign_vector_string(vector_string)
1155 self.base_score = self.calculate_base_score()
1156
1157 @hybrid_property
1158 def base_score(self):
1159 if self._base_score is not None:
1160 return self._base_score
1161 return self._fixed_base_score
1162
1163 @base_score.setter
1164 def base_score(self, base_score):
1165 self._base_score = base_score
1166
1167 def assign_vector_string(self, vector_string, base_score):
1168 raise NotImplementedError
1169
1170 def calculate_base_score(self):
1171 raise NotImplementedError
1172
1173 def __repr__(self):
1174 return f'{self.vector_string}'
1175
1176
1177 class CVSSV2(CVSSBase):
1178 __tablename__ = "cvss_v2"
1179 id = Column(Integer, ForeignKey('cvss_base.id'), primary_key=True)
1180 access_vector = Column(Enum(*CVSS2GeneralConfig.ACCESS_VECTOR_TYPES, name="cvss_access_vector"))
1181 access_complexity = Column(Enum(*CVSS2GeneralConfig.ACCESS_COMPLEXITY_TYPES, name="cvss_access_complexity"))
1182 authentication = Column(Enum(*CVSS2GeneralConfig.AUTHENTICATION_TYPES, name="cvss_authentication"))
1183 confidentiality_impact = Column(Enum(*CVSS2GeneralConfig.IMPACT_TYPES_V2, name="cvss_impact_types_v2"))
1184 integrity_impact = Column(Enum(*CVSS2GeneralConfig.IMPACT_TYPES_V2, name="cvss_impact_types_v2"))
1185 availability_impact = Column(Enum(*CVSS2GeneralConfig.IMPACT_TYPES_V2, name="cvss_impact_types_v2"))
1186
1187 __mapper_args__ = {
1188 'polymorphic_identity': "v2"
1189 }
1190
1191 def __init__(self, base_score: Float = None, vector_string=None, **kwargs):
1192 super().__init__(version=CVSS2GeneralConfig.VERSION, vector_string=vector_string,
1193 _fixed_base_score=base_score, **kwargs)
1194
1195 def assign_vector_string(self, vector_string):
1196 self._vector_string = vector_string
1197 vector_string_parsed = re.match(CVSS2GeneralConfig.PATTERN, vector_string if vector_string else '')
1198 if vector_string_parsed:
1199 self.access_vector = vector_string_parsed['access_vector']
1200 self.access_complexity = vector_string_parsed['access_complexity']
1201 self.authentication = vector_string_parsed['authentication']
1202 self.confidentiality_impact = vector_string_parsed['confidentiality']
1203 self.integrity_impact = vector_string_parsed['integrity']
1204 self.availability_impact = vector_string_parsed['availability']
1205 else:
1206 self.access_vector = None
1207 self.access_complexity = None
1208 self.authentication = None
1209 self.confidentiality_impact = None
1210 self.integrity_impact = None
1211 self.availability_impact = None
1212
1213 def exploitability(self):
1214 return 20 * CVSS2GeneralConfig.ACCESS_VECTOR_SCORE[self.access_vector] * CVSS2GeneralConfig.ACCESS_COMPLEXITY_SCORE[self.access_complexity] * CVSS2GeneralConfig.AUTHENTICATION_SCORE[self.authentication]
1215
1216 def impact(self):
1217 return 10.41 * (1 - (1 - CVSS2GeneralConfig.IMPACT_SCORES_V2[self.confidentiality_impact]) * (1 - CVSS2GeneralConfig.IMPACT_SCORES_V2[self.integrity_impact]) * (1 - CVSS2GeneralConfig.IMPACT_SCORES_V2[self.availability_impact]))
1218
1219 def fimpact(self):
1220 if self.impact() == 0:
1221 return 0
1222 return 1.176
1223
1224 def calculate_base_score(self):
1225 if re.match(CVSS2GeneralConfig.PATTERN, self.vector_string if self.vector_string else ''):
1226 score = (0.6 * self.impact() + 0.4 * self.exploitability() - 1.5) * self.fimpact()
1227 return round(score, 1) # pylint: disable=round-builtin
1228 return None
1229
1230
1231 class CVSSV3(CVSSBase):
1232 __tablename__ = "cvss_v3"
1233 id = Column(Integer, ForeignKey('cvss_base.id'), primary_key=True)
1234 attack_vector = Column(Enum(*CVSS3GeneralConfig.ATTACK_VECTOR_TYPES, name="cvss_attack_vector"))
1235 attack_complexity = Column(Enum(*CVSS3GeneralConfig.ATTACK_COMPLEXITY_TYPES, name="cvss_attack_complexity"))
1236 privileges_required = Column(Enum(*CVSS3GeneralConfig.PRIVILEGES_REQUIRED_TYPES, name="cvss_privileges_required"))
1237 user_interaction = Column(Enum(*CVSS3GeneralConfig.USER_INTERACTION_TYPES, name="cvss_user_interaction"))
1238 scope = Column(Enum(*CVSS3GeneralConfig.SCOPE_TYPES, name="cvss_scope"))
1239 confidentiality_impact = Column(Enum(*CVSS3GeneralConfig.IMPACT_TYPES_V3, name="cvss_impact_types_v3"))
1240 integrity_impact = Column(Enum(*CVSS3GeneralConfig.IMPACT_TYPES_V3, name="cvss_impact_types_v3"))
1241 availability_impact = Column(Enum(*CVSS3GeneralConfig.IMPACT_TYPES_V3, name="cvss_impact_types_v3"))
1242
1243 __mapper_args__ = {
1244 'polymorphic_identity': "v3"
1245 }
1246
1247 def __init__(self, base_score: Float = None, vector_string=None, **kwargs):
1248 super().__init__(version=CVSS3GeneralConfig.VERSION, vector_string=vector_string,
1249 _fixed_base_score=base_score, **kwargs)
1250
1251 def assign_vector_string(self, vector_string):
1252 self._vector_string = vector_string
1253 vector_string_parsed = re.match(CVSS3GeneralConfig.PATTERN, vector_string if vector_string else '')
1254 if vector_string_parsed:
1255 self.attack_vector = vector_string_parsed['attack_vector']
1256 self.attack_complexity = vector_string_parsed['attack_complexity']
1257 self.privileges_required = vector_string_parsed['privileges_required']
1258 self.user_interaction = vector_string_parsed['user_interaction']
1259 self.scope = vector_string_parsed['scope']
1260 self.confidentiality_impact = vector_string_parsed['confidentiality']
1261 self.integrity_impact = vector_string_parsed['integrity']
1262 self.availability_impact = vector_string_parsed['availability']
1263 else:
1264 self.attack_vector = None
1265 self.attack_complexity = None
1266 self.privileges_required = None
1267 self.user_interaction = None
1268 self.scope = None
1269 self.confidentiality_impact = None
1270 self.integrity_impact = None
1271 self.availability_impact = None
1272
1273 def isc_base(self):
1274 return 1 - ((1 - CVSS3GeneralConfig.IMPACT_SCORES_V3[self.confidentiality_impact]) * (1 - CVSS3GeneralConfig.IMPACT_SCORES_V3[self.integrity_impact]) * (1 - CVSS3GeneralConfig.IMPACT_SCORES_V3[self.availability_impact]))
1275
1276 def impact(self):
1277 if self.scope == CVSS3GeneralConfig.UNCHANGED:
1278 return 6.42 * self.isc_base()
1279 else:
1280 return 7.52 * (self.isc_base() - 0.029) - 3.25 * (self.isc_base() - 0.02) ** 15
1281
1282 def exploitability(self):
1283 return 8.22 * CVSS3GeneralConfig.ATTACK_VECTOR_SCORES[self.attack_vector] * CVSS3GeneralConfig.ATTACK_COMPLEXITY_SCORES[self.attack_complexity] * CVSS3GeneralConfig.PRIVILEGES_REQUIRED_SCORES[self.scope][self.privileges_required] * CVSS3GeneralConfig.USER_INTERACTION_SCORES[self.user_interaction]
1284
1285 def calculate_base_score(self):
1286 if re.match(CVSS3GeneralConfig.PATTERN, self.vector_string if self.vector_string else ''):
1287 score = 10
1288 if self.impact() <= 0:
1289 return 0.0
1290 impact_plus_exploitability = self.impact() + self.exploitability()
1291 if self.scope == CVSS3GeneralConfig.UNCHANGED:
1292 if impact_plus_exploitability < 10:
1293 score = impact_plus_exploitability
1294 else:
1295 impact_plus_exploitability = impact_plus_exploitability * 1.08
1296 if impact_plus_exploitability < 10:
1297 score = impact_plus_exploitability
1298
1299 # round up score
1300 # Where “Round up” is defined as the smallest number, specified to one decimal place,
1301 # that is equal to or higher than its input. For example, Round up (4.02) is 4.1; and Round up (4.00) is 4.0.
1302 return math.ceil(score * 10) / 10
1303 return None
1304
1305
10351306 class Service(Metadata):
10361307 STATUSES = [
10371308 'open',
10511322
10521323 banner = BlankColumn(Text)
10531324
1054 host_id = Column(Integer, ForeignKey('host.id'), index=True, nullable=False)
1325 host_id = Column(Integer, ForeignKey('host.id', ondelete='CASCADE'), index=True, nullable=False)
10551326 host = relationship(
10561327 'Host',
10571328 foreign_keys=[host_id],
10881359
10891360
10901361 class VulnerabilityGeneric(VulnerabilityABC):
1362 STATUS_OPEN = 'open'
1363 STATUS_RE_OPENED = 're-opened'
1364 STATUS_CLOSED = 'closed'
1365 STATUS_RISK_ACCEPTED = 'risk-accepted'
1366
10911367 STATUSES = [
1092 'open',
1093 'closed',
1094 're-opened',
1095 'risk-accepted'
1368 STATUS_OPEN,
1369 STATUS_CLOSED,
1370 STATUS_RE_OPENED,
1371 STATUS_RISK_ACCEPTED
10961372 ]
10971373 VULN_TYPES = [
10981374 'vulnerability',
11211397
11221398 vulnerability_duplicate_id = Column(
11231399 Integer,
1124 ForeignKey('vulnerability.id'),
1400 ForeignKey('vulnerability.id', ondelete='SET NULL'),
11251401 index=True,
11261402 nullable=True,
11271403 )
11311407
11321408 vulnerability_template_id = Column(
11331409 Integer,
1134 ForeignKey('vulnerability_template.id'),
1410 ForeignKey('vulnerability_template.id', ondelete='SET NULL'),
11351411 index=True,
11361412 nullable=True,
11371413 )
11551431 cve = association_proxy('cve_instances',
11561432 'name',
11571433 proxy_factory=CustomAssociationSet,
1158 creator=_build_associationproxy_creator_non_workspaced('CVE'))
1434 creator=_build_associationproxy_creator_non_workspaced('CVE', lambda c: c.upper()))
1435
1436 # TODO: Ver si el nombre deberia ser cvss_v2_id
1437 cvssv2_id = Column(
1438 Integer,
1439 ForeignKey('cvss_v2.id'),
1440 nullable=True
1441 )
1442 cvssv2 = relationship('CVSSV2', backref=backref('vulnerability_cvssv2'))
1443
1444 # TODO: Ver si el nombre deberia ser cvss_v3_id
1445 cvssv3_id = Column(
1446 Integer,
1447 ForeignKey('cvss_v3.id'),
1448 nullable=True
1449 )
1450 cvssv3 = relationship('CVSSV3', backref=backref('vulnerability_cvssv3'))
11591451
11601452 reference_instances = relationship(
11611453 "Reference",
12491541 'host_inner.id = service.host_id'))
12501542 )
12511543
1252 host_id = Column(Integer, ForeignKey(Host.id), index=True)
1544 host_id = Column(Integer, ForeignKey(Host.id, ondelete='CASCADE'), index=True)
12531545 host = relationship(
12541546 'Host',
12551547 backref=backref("vulnerabilities", cascade="all, delete-orphan"),
13101602
13111603 @declared_attr
13121604 def service_id(cls):
1313 return VulnerabilityGeneric.__table__.c.get('service_id', Column(Integer, db.ForeignKey('service.id'),
1314 index=True))
1605 return VulnerabilityGeneric.__table__.c.get('service_id',
1606 Column(Integer,
1607 db.ForeignKey('service.id', ondelete='CASCADE'),
1608 index=True))
13151609
13161610 @declared_attr
13171611 def service(cls):
13401634 @declared_attr
13411635 def service_id(cls):
13421636 return VulnerabilityGeneric.__table__.c.get(
1343 'service_id', Column(Integer, db.ForeignKey('service.id'),
1637 'service_id', Column(Integer, db.ForeignKey('service.id', ondelete='CASCADE'),
13441638 nullable=False))
13451639
13461640 @declared_attr
13661660 start_line = Column(Integer, nullable=True)
13671661 end_line = Column(Integer, nullable=True)
13681662
1369 source_code_id = Column(Integer, ForeignKey(SourceCode.id), index=True)
1663 source_code_id = Column(Integer, ForeignKey(SourceCode.id, ondelete='CASCADE'), index=True)
13701664 source_code = relationship(
13711665 SourceCode,
13721666 backref='vulnerabilities',
14291723 class ReferenceVulnerabilityAssociation(db.Model):
14301724 __tablename__ = 'reference_vulnerability_association'
14311725
1432 vulnerability_id = Column(Integer, ForeignKey('vulnerability.id'), primary_key=True)
1726 vulnerability_id = Column(Integer, ForeignKey('vulnerability.id', ondelete="CASCADE"), primary_key=True)
14331727 reference_id = Column(Integer, ForeignKey('reference.id'), primary_key=True)
14341728
14351729 reference = relationship("Reference",
14461740 class PolicyViolationVulnerabilityAssociation(db.Model):
14471741 __tablename__ = 'policy_violation_vulnerability_association'
14481742
1449 vulnerability_id = Column(Integer, ForeignKey('vulnerability.id'), primary_key=True)
1743 vulnerability_id = Column(Integer, ForeignKey('vulnerability.id', ondelete="CASCADE"), primary_key=True)
14501744 policy_violation_id = Column(Integer, ForeignKey('policy_violation.id'), primary_key=True)
14511745
14521746 policy_violation = relationship("PolicyViolation", backref=backref("policy_violation_associations", cascade="all, delete-orphan"), foreign_keys=[policy_violation_id])
15381832 description = BlankColumn(Text)
15391833 name = BlankColumn(Text)
15401834
1541 host_id = Column(Integer, ForeignKey(Host.id), index=True, nullable=True)
1835 host_id = Column(Integer, ForeignKey(Host.id, ondelete='CASCADE'), index=True, nullable=True)
15421836 host = relationship(
15431837 'Host',
15441838 backref=backref("credentials", cascade="all, delete-orphan"),
15451839 foreign_keys=[host_id])
15461840
1547 service_id = Column(Integer, ForeignKey(Service.id), index=True, nullable=True)
1841 service_id = Column(Integer, ForeignKey(Service.id, ondelete='CASCADE'), index=True, nullable=True)
15481842 service = relationship(
15491843 'Service',
15501844 backref=backref('credentials', cascade="all, delete-orphan"),
16041898 'association_workspace_and_agents_table',
16051899 db.Model.metadata,
16061900 Column('workspace_id', Integer, ForeignKey('workspace.id')),
1607 Column('agent_id', Integer, ForeignKey('agent.id'))
1901 Column('agent_id', Integer, ForeignKey('agent.id', ondelete='CASCADE'))
16081902 )
16091903
16101904
16741968 FROM association_workspace_and_agents_table as assoc
16751969 JOIN agent ON agent.id = assoc.agent_id and assoc.workspace_id = workspace.id
16761970 WHERE agent.active is TRUE
1677 ) AS active_agents_count,
1971 ) AS run_agent_date,
1972 (SELECT executor.last_run
1973 FROM executor
1974 JOIN agent ON executor.agent_id = agent.id
1975 JOIN association_workspace_and_agents_table ON
1976 agent.id = association_workspace_and_agents_table.agent_id
1977 and association_workspace_and_agents_table.workspace_id = workspace.id
1978 WHERE executor.last_run is not null
1979 ORDER BY executor.last_run DESC
1980 LIMIT 1
1981 ) AS last_run_agent_date,
16781982 p_4.count_3 as open_services,
16791983 p_4.count_4 as total_service_count,
16801984 p_5.count_5 as vulnerability_web_count,
16871991 p_5.count_12 as vulnerability_low_count,
16881992 p_5.count_13 as vulnerability_informational_count,
16891993 p_5.count_14 as vulnerability_unclassified_count,
1994 p_5.count_15 as vulnerability_open_count,
1995 p_5.count_16 as vulnerability_confirmed_count,
16901996 workspace.create_date AS workspace_create_date,
16911997 workspace.update_date AS workspace_update_date,
16921998 workspace.id AS workspace_id,
17192025 COUNT(case when vulnerability.severity = 'medium' then 1 else null end) as count_11,
17202026 COUNT(case when vulnerability.severity = 'low' then 1 else null end) as count_12,
17212027 COUNT(case when vulnerability.severity = 'informational' then 1 else null end) as count_13,
1722 COUNT(case when vulnerability.severity = 'unclassified' then 1 else null end) as count_14
2028 COUNT(case when vulnerability.severity = 'unclassified' then 1 else null end) as count_14,
2029 COUNT(case when vulnerability.status = 'open' OR vulnerability.status='re-opened' then 1 else null end) as count_15,
2030 COUNT(case when vulnerability.confirmed is True then 1 else null end) as count_16
17232031 FROM vulnerability
17242032 RIGHT JOIN workspace w ON vulnerability.workspace_id = w.id
17252033 WHERE 1=1 {0}
21002408
21012409 text = BlankColumn(Text)
21022410
2103 reply_to_id = Column(Integer, ForeignKey('comment.id'))
2411 reply_to_id = Column(Integer, ForeignKey('comment.id', ondelete='SET NULL'))
21042412 reply_to = relationship(
21052413 'Comment',
21062414 remote_side=[id],
24832791 __tablename__ = 'executor'
24842792 id = Column(Integer, primary_key=True)
24852793 name = Column(String, nullable=False)
2486 agent_id = Column(Integer, ForeignKey('agent.id'), index=True, nullable=False)
2794 agent_id = Column(Integer, ForeignKey('agent.id', ondelete='CASCADE'), index=True, nullable=False)
24872795 agent = relationship(
24882796 'Agent',
24892797 backref=backref('executors', cascade="all, delete-orphan"),
26042912 backref=backref('agent_executions', cascade="all, delete-orphan")
26052913 )
26062914 parameters_data = Column(JSONType, nullable=False)
2607 command_id = Column(Integer, ForeignKey('command.id'), index=True)
2915 command_id = Column(Integer, ForeignKey('command.id', ondelete='SET NULL'), index=True)
26082916 command = relationship(
26092917 'Command',
26102918 foreign_keys=[command_id],
26472955 end = Column(DateTime, nullable=True)
26482956 rule_id = Column(Integer, ForeignKey('rule.id'), index=True, nullable=False)
26492957 rule = relationship('Rule', foreign_keys=[rule_id], backref=backref('executions', cascade="all, delete-orphan"))
2650 command_id = Column(Integer, ForeignKey('command.id'), index=True, nullable=False)
2958 command_id = Column(Integer, ForeignKey('command.id', ondelete='CASCADE'), index=True, nullable=False)
26512959 command = relationship('Command', foreign_keys=[command_id],
26522960 backref=backref('rule_executions', cascade="all, delete-orphan"))
26532961
361361 key: value for key, value in data.items()
362362 if value
363363 }
364
365 # I'm Py3
194194 if not Path(str(FARADAY_SERVER_PID_FILE).format(port)).exists():
195195 return None
196196
197 with open(str(FARADAY_SERVER_PID_FILE).format(port), 'r') as pid_file:
197 with open(str(FARADAY_SERVER_PID_FILE).format(port)) as pid_file:
198198 # If PID file is badly written, delete it and
199199 # assume server is not running
200200 try:
6767
6868 # Add wildcards to both ends of a search term
6969 if is_direct_filter_search:
70 like_str = u'%' + field_filter.get(attribute) + u'%'
70 like_str = '%' + field_filter.get(attribute) + '%'
7171 elif is_free_text_search:
72 like_str = u'%' + free_text_search + u'%'
72 like_str = '%' + free_text_search + '%'
7373 else:
7474 continue
7575
167167 if instance:
168168 return instance, False
169169 else:
170 params = dict((k, v) for k, v in kwargs.items() if not isinstance(v, ClauseElement))
170 params = {k: v for k, v in kwargs.items() if not isinstance(v, ClauseElement)}
171171 params.update(defaults or {})
172172 instance = model(**params)
173173 session.add(instance)
185185 else:
186186 separator = ','
187187
188 res = 'array_to_string(array_agg({0}), \'{1}\')'.format(
188 res = 'array_to_string(array_agg({}), \'{}\')'.format(
189189 compiler.process(element.clauses.clauses[0]),
190190 separator,
191191 )
4242 # uncomment this to see who's calling what
4343 # ps.print_callers()
4444 debug_logger.debug(s.getvalue())
45
46 # I'm Py3
0 # Standard library imports
01 import csv
2 import logging
13 from io import StringIO, BytesIO
2 import logging
3
4
5 # Local application imports
46 from faraday.server.models import (
57 db,
68 Comment,
1921 "target", "desc", "status", "hostnames", "comments", "owner",
2022 "os", "resolution", "refs", "easeofresolution", "web_vulnerability",
2123 "data", "website", "path", "status_code", "request", "response", "method",
22 "params", "pname", "query", "policyviolations", "external_id", "impact_confidentiality",
24 "params", "pname", "query", "cve", "policyviolations", "external_id", "impact_confidentiality",
2325 "impact_integrity", "impact_availability", "impact_accountability", "update_date"
2426 ]
2527
182184 "params": vuln.get('params', None),
183185 "pname": vuln.get('pname', None),
184186 "query": vuln.get('query', None),
187 "cve": vuln.get('cve', None),
185188 "policyviolations": vuln.get('policyviolations', None),
186189 "external_id": vuln.get('external_id', None),
187190 "impact_confidentiality": vuln["impact"]["confidentiality"],
203206 # Patch possible formula injection attacks
204207 def csv_escape(vuln_dict):
205208 for key, value in vuln_dict.items():
206 if str(value).startswith('=') or str(value).startswith('+') or str(value).startswith('-') or str(value).startswith('@'):
209 if str(value).startswith('=') or str(value).startswith('+') or str(value).startswith('-') \
210 or str(value).startswith('@'):
207211 # Convert value to str just in case is has another type (like a list or
208212 # dict). This would be done anyway by the csv writer.
209213 vuln_dict[key] = "'" + str(value)
2323 from faraday.server.fields import JSONType
2424
2525
26 VALID_OPERATORS = set(OPERATORS.keys()) - set(['desc', 'asc'])
26 VALID_OPERATORS = set(OPERATORS.keys()) - {'desc', 'asc'}
2727
2828 logger = logging.getLogger(__name__)
2929
269269 an error on PostgreSQL
270270 """
271271 if 'group_by' in data and 'order_by' in data:
272 group_by_fields = set(group_field['field'] for group_field in data['group_by'])
273 order_by_fields = set(order_field['field'] for order_field in data['order_by'])
272 group_by_fields = {group_field['field'] for group_field in data['group_by']}
273 order_by_fields = {order_field['field'] for order_field in data['order_by']}
274274 if not order_by_fields.issubset(group_by_fields):
275275 logger.error(f'All order fields ({order_by_fields}) must be in group by {group_by_fields}.')
276276 raise ValidationError(f'All order fields ({order_by_fields}) must be in group by {group_by_fields}.')
7777
7878
7979 setup_logging()
80
81
82 # I'm Py3
223223
224224 def __repr__(self):
225225 """Returns a string representation of this object."""
226 return '<Filter {0} {1} {2}>'.format(self.fieldname, self.operator,
226 return '<Filter {} {} {}>'.format(self.fieldname, self.operator,
227227 self.argument or self.otherfield)
228228
229229 @staticmethod
99 from twisted.web.resource import Resource, ForbiddenResource
1010
1111 from twisted.internet import reactor, error
12 from twisted.web.server import Site
1213 from twisted.web.static import File
1314 from twisted.web.util import Redirect
1415 from twisted.web.http import proxiedLogFormatter
3233 FARADAY_APP = None
3334
3435 logger = logging.getLogger(__name__)
36
37
38 class FaradaySite(Site):
39 def getResourceFor(self, request):
40 resource = super().getResourceFor(request)
41 if isinstance(resource, twisted.web.resource.NoResource):
42 resource = self.resource.getChild("index.html", request)
43 return resource
3544
3645
3746 class CleanHttpHeadersResource(Resource):
130139 self.stop_threads()
131140
132141 log_path = CONST_FARADAY_HOME_PATH / 'logs' / 'access-logging.log'
133 site = twisted.web.server.Site(self.root_resource,
134 logPath=log_path,
135 logFormatter=proxiedLogFormatter)
142 site = FaradaySite(self.root_resource, logPath=log_path, logFormatter=proxiedLogFormatter)
136143 site.displayTracebacks = False
137144
138145 try:
105105 return self.factory.join_agent(self, agent)
106106 if message['action'] == 'LEAVE_AGENT':
107107 with get_app().app_context():
108 (agent_id,) = [
108 (agent_id,) = (
109109 k
110110 for (k, v) in connected_agents.items()
111111 if v == self
112 ]
112 )
113113 agent = Agent.query.get(agent_id)
114114 assert agent is not None # TODO the agent could be deleted here
115115 return self.factory.leave_agent(self, agent)
119119 logger.warning(f'Missing executor_name param in message: {message}')
120120 return True
121121
122 (agent_id,) = [
122 (agent_id,) = (
123123 k
124124 for (k, v) in connected_agents.items()
125125 if v == self
126 ]
126 )
127127 agent = Agent.query.get(agent_id)
128128 assert agent is not None # TODO the agent could be deleted here
129129
4343 settings_config.update(query.value)
4444 settings_config = self.clear_configuration(settings_config)
4545 return settings_config
46
47 def delete_configuration(self):
48 from faraday.server.web import get_app # pylint: disable=import-outside-toplevel
49 with get_app().app_context():
50 db.session.query(Configuration).filter(Configuration.key == self.settings_key).delete()
51 db.session.commit()
52 self.__class__.value.fget.cache_clear()
4653
4754 def get_default_config(self):
4855 return {}
0
10 class MissingConfigurationError(Exception):
21 """Raised when setting configuration is missing"""
32 pass
1313 pname =
1414 "faraday-agent-parameters-types";
1515 version =
16 "1.0.2";
16 "1.0.3";
1717
1818 src =
1919 fetchPypi {
2222 pname =
2323 "faraday_agent_parameters_types";
2424 sha256 =
25 "0dw2s7lyg9s1qjj6yrn5hxpasbb32qg89pcfsv4vv47yla9djzyc";
25 "1rz0mrpgg529fd7ppi9fkpgvmfriwamlx0ah10637hvpnjfncmb1";
2626 };
2727
2828 buildInputs =
2020 pname =
2121 "faraday-plugins";
2222 version =
23 "1.5.5";
23 "1.5.9";
2424
2525 src =
2626 fetchPypi {
2828 pname
2929 version;
3030 sha256 =
31 "1dw7j8zfa8j9m0qcpyzl6k0z29k3j5i90lyfg2zapwkpalkgx0d1";
31 "0wkwan2vg7np0z1pwskpgwgnxxk9d46ffq1a3f5ai12ibwj9kwgh";
3232 };
3333
3434 propagatedBuildInputs =
6464 pname =
6565 "faradaysec";
6666 version =
67 "3.18.1";
67 "3.19.0";
6868
6969 src =
7070 lib.cleanSource
1212 pname =
1313 "marshmallow-sqlalchemy";
1414 version =
15 "0.26.1";
15 "0.27.0";
1616
1717 src =
1818 fetchPypi {
2020 pname
2121 version;
2222 sha256 =
23 "0wval5lqak31zwrzmgi9c919lqk0dw1zxvwihif4nmaivrs5ylnq";
23 "0za0zl1vyphx2pnf2zcwbjp1lzqkdi2gcf1saa668i24aqlv288m";
2424 };
2525
2626 propagatedBuildInputs =
1212 pname =
1313 "python-socketio";
1414 version =
15 "5.4.1";
15 "5.5.0";
1616
1717 src =
1818 fetchPypi {
2020 pname
2121 version;
2222 sha256 =
23 "1c17cvm91map3rbgl5156y6zwzz2wyqvm31298a23d7bvwyjfkpg";
23 "02ygri5qaw7ynqlnimn3b0arl6r5bh6wyc0dl4gq389ap2hjx5yf";
2424 };
2525
2626 propagatedBuildInputs =
3232 simplekv>=0.13.0
3333 Flask-KVSession-fork>=0.6.3
3434 distro>=1.4.0
35 faraday-plugins>=1.4.4,<2.0.0
36 apispec>=4.0.0
35 faraday-plugins>=1.5.9,<2.0.0
36 apispec>=4.0.0,<5.0.0
3737 apispec-webframeworks>=0.5.0
3838 pyyaml
3939 Flask-SocketIO>=5.0.1
4040 pyotp>=2.6.0
4141 Flask-Limiter
4242 Flask-Mail
43 faraday_agent_parameters_types>=1.0.0
43 faraday_agent_parameters_types>=1.0.3
1010 # It ensures open() defaults to text mode with universal newlines,
1111 # and accepts an argument to specify the text encoding
1212 # Python 3 only projects can skip this import
13 from io import open
1413 from re import search
1514
1615 # Get the long description from the README file
2423
2524 To read about the latest features check out the [release notes](https://github.com/infobyte/faraday/blob/master/RELEASE.md)!"""
2625
27 with open('faraday/__init__.py', 'rt', encoding='utf8') as f:
26 with open('faraday/__init__.py', encoding='utf8') as f:
2827 version = search(r'__version__ = \'(.*?)\'', f.read()).group(1)
2928
3029 # Taken from https://stackoverflow.com/questions/14399534/reference-requirements-txt-for-the-install-requires-kwarg-in-setuptools-setup-py/14399775#14399775
298298 def csrf_token(logged_user, test_client):
299299 session_response = test_client.get('/session')
300300 return session_response.json.get('csrf_token')
301
302 # I'm Py3
33 See the file 'doc/LICENSE' for the license information
44
55 '''
6 from builtins import chr, range
76
87 import random
98 import string
5352 Action,
5453 RuleAction,
5554 Condition,
56 Role
55 Role, RuleExecution
5756 )
5857
5958
132131 class WorkspaceFactory(FaradayFactory):
133132
134133 name = FuzzyText(chars=string.ascii_lowercase + string.digits)
134 description = FuzzyText()
135135 creator = factory.SubFactory(UserFactory)
136136
137137 class Meta:
338338
339339 host = factory.SubFactory(HostFactory, workspace=factory.SelfAttribute('..workspace'))
340340 service = factory.SubFactory(ServiceFactory, workspace=factory.SelfAttribute('..workspace'))
341 description = FuzzyText()
342341 type = "vulnerability"
343342
344343 @classmethod
668667 model = RuleAction
669668 sqlalchemy_session = db.session
670669
670
671 class RuleExecutionFactory(FaradayFactory):
672 rule = factory.SubFactory(RuleFactory)
673 command = factory.SubFactory(CommandFactory)
674
675 class Meta:
676 model = RuleExecution
677 sqlalchemy_session = db.session
678
671679 # I'm Py3
5555 assert len(vulnerability_web.evidence) == 1
5656 assert vulnerability_web.evidence[0].object_type == 'vulnerability'
5757 assert vulnerability_web.evidence[0].object_id == vulnerability_web.id
58 # I'm Py3
8484 session.commit()
8585 assert len(session.new) == len(session.deleted) == len(
8686 session.dirty) == 0
87 assert set(new_value) == set(hn.name for hn in
88 host_with_hostnames.hostnames)
87 assert set(new_value) == {hn.name for hn in
88 host_with_hostnames.hostnames}
8989
9090 def test_all(self, host, session):
9191 a = Hostname(workspace=host.workspace, host=host, name='a')
106106 assert c.name == 'c'
107107
108108 session.commit()
109 assert set(hn.name for hn in host.hostnames) == {'b', 'c'}
109 assert {hn.name for hn in host.hostnames} == {'b', 'c'}
110110
111111 def test_change_one(self, host, session):
112112 hn = Hostname(workspace=host.workspace,
250250 session.commit()
251251 assert vulnerability.creator_command_id == command.id
252252 assert vulnerability.creator_command_tool == command.tool
253 # I'm Py3
55
66 from unittest import mock
77
8 from posixpath import join as urljoin
8 from posixpath import join
9 from urllib.parse import urljoin
910 import pyotp
1011 import pytest
1112
240241 assert '405' in exc_info.value.args[0]
241242
242243 def workspaced_url(self, workspace, obj=None):
243 url = API_PREFIX + workspace.name + '/' + self.api_endpoint
244 url = urljoin(API_PREFIX, f"{workspace.name}{self.api_endpoint}")
244245 if obj is not None:
245246 id_ = str(obj.id) if isinstance(obj, self.model) else str(obj)
246 url += u'/' + id_
247 url = urljoin(url, id_)
247248 return url
248249
249250 def create_raw_agent(self, active=False, token="TOKEN",
393394 },
394395 }
395396 res = test_client.post(
396 self.url(agent) + 'run/',
397 join(self.url(agent), 'run'),
397398 json=payload
398399 )
399400 assert res.status_code == 404
451452 'csrf_token': csrf_token
452453 }
453454 res = test_client.post(
454 urljoin(self.url(agent), 'run'),
455 join(self.url(agent), 'run'),
455456 json=payload
456457 )
457458 assert res.status_code == 400
473474 }
474475
475476 res = test_client.post(
476 urljoin(self.url(agent), 'run'),
477 join(self.url(agent), 'run'),
477478 json=payload
478479 )
479480
484485 session.add(agent)
485486 session.commit()
486487 res = test_client.post(
487 urljoin(self.url(agent), 'run'),
488 join(self.url(agent), 'run'),
488489 data='[" broken]"{'
489490 )
490491 assert res.status_code == 400
506507 ('content-type', 'text/html'),
507508 ]
508509 res = test_client.post(
509 urljoin(self.url(agent), 'run'),
510 join(self.url(agent), 'run'),
510511 data=payload,
511512 headers=headers)
512513 assert res.status_code == 400
525526 },
526527 }
527528 res = test_client.post(
528 urljoin(self.url(agent), 'run'),
529 join(self.url(agent), 'run'),
529530 json=payload
530531 )
531532 assert res.status_code == 400
552553 },
553554 }
554555 res = test_client.post(
555 urljoin(self.url(agent), 'run'),
556 join(self.url(agent), 'run'),
556557 json=payload
557558 )
558559 assert res.status_code == 200
581582 },
582583 }
583584 res = test_client.post(
584 urljoin(self.url(agent), 'run'),
585 join(self.url(agent), 'run'),
585586 json=payload
586587 )
587588 assert res.status_code == 400
596597 'executorData': '[][dassa',
597598 }
598599 res = test_client.post(
599 urljoin(self.url(agent), 'run'),
600 join(self.url(agent), 'run'),
600601 json=payload
601602 )
602603 assert res.status_code == 400
610611 'executorData': '',
611612 }
612613 res = test_client.post(
613 urljoin(self.url(agent), 'run'),
614 join(self.url(agent), 'run'),
614615 json=payload
615616 )
616617 assert res.status_code == 400
619620 agent = AgentFactory.create(workspaces=[self.workspace])
620621 session.add(agent)
621622 session.commit()
622 res = test_client.get(urljoin(self.url(), 'get_manifests'))
623 res = test_client.get(join(self.url(), 'get_manifests'))
623624 assert res.status_code == 200
33 See the file 'doc/LICENSE' for the license information
44
55 '''
6 from builtins import str
76 import base64
87
98 import pytest
0 # -*- coding: utf8 -*-
10 '''
21 Faraday Penetration Test IDE
32 Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/)
5049 assert res.status_code == 200
5150 assert 'commands' in res.json
5251 for command in res.json['commands']:
53 assert set([u'id', u'key', u'value']) == set(command.keys())
52 assert {'id', 'key', 'value'} == set(command.keys())
5453 object_properties = [
55 u'_id',
56 u'command',
57 u'duration',
58 u'hostname',
59 u'ip',
60 u'itime',
61 u'params',
62 u'user',
63 u'workspace',
64 u'tool',
65 u'import_source',
66 u'creator',
67 u'metadata'
54 '_id',
55 'command',
56 'duration',
57 'hostname',
58 'ip',
59 'itime',
60 'params',
61 'user',
62 'workspace',
63 'tool',
64 'import_source',
65 'creator',
66 'metadata'
6867 ]
6968 assert command['value']['workspace'] == self.workspace.name
7069 assert set(object_properties) == set(command['value'].keys())
9695 assert res.status_code == 200
9796
9897 assert list(filter(lambda stats: stats['_id'] == command.id, res.json)) == [
99 {u'_id': command.id,
100 u'command': command.command,
101 u'import_source': u'shell',
102 u'user': command.user,
103 u'date': time.mktime(command.start_date.timetuple()) * 1000,
104 u'params': command.params,
105 u'tool': command.tool,
106 u'hosts_count': 1,
107 u'services_count': 0,
108 u'vulnerabilities_count': 1,
109 u'criticalIssue': 0}]
98 {'_id': command.id,
99 'command': command.command,
100 'import_source': 'shell',
101 'user': command.user,
102 'date': time.mktime(command.start_date.timetuple()) * 1000,
103 'params': command.params,
104 'tool': command.tool,
105 'hosts_count': 1,
106 'services_count': 0,
107 'vulnerabilities_count': 1,
108 'criticalIssue': 0}]
110109
111110 assert list(filter(lambda stats: stats['_id'] == another_command.id,
112111 res.json)) == [{
113 u'_id': another_command.id,
114 u'command': another_command.command,
115 u'import_source': u'shell',
116 u'tool': another_command.tool,
117 u'user': another_command.user,
118 u'date': time.mktime(
112 '_id': another_command.id,
113 'command': another_command.command,
114 'import_source': 'shell',
115 'tool': another_command.tool,
116 'user': another_command.user,
117 'date': time.mktime(
119118 another_command.start_date.timetuple()) * 1000,
120 u'params': another_command.params,
121 u'hosts_count': 0,
122 u'services_count': 0,
123 u'vulnerabilities_count': 0,
124 u'criticalIssue': 0}]
119 'params': another_command.params,
120 'hosts_count': 0,
121 'services_count': 0,
122 'vulnerabilities_count': 0,
123 'criticalIssue': 0}]
125124
126125 def test_verify_created_critical_vulns_is_correctly_showing_sum_values(self, session, test_client):
127126 workspace = WorkspaceFactory.create()
152151 res = test_client.get(urljoin(self.url(workspace=command.workspace), 'activity_feed'))
153152 assert res.status_code == 200
154153 assert res.json == [
155 {u'_id': command.id,
156 u'command': command.command,
157 u'import_source': u'shell',
158 u'tool': command.tool,
159 u'user': command.user,
160 u'date': time.mktime(command.start_date.timetuple()) * 1000,
161 u'params': command.params,
162 u'hosts_count': 1,
163 u'services_count': 0,
164 u'vulnerabilities_count': 2,
165 u'criticalIssue': 1}
154 {'_id': command.id,
155 'command': command.command,
156 'import_source': 'shell',
157 'tool': command.tool,
158 'user': command.user,
159 'date': time.mktime(command.start_date.timetuple()) * 1000,
160 'params': command.params,
161 'hosts_count': 1,
162 'services_count': 0,
163 'vulnerabilities_count': 2,
164 'criticalIssue': 1}
166165 ]
167166
168167 def test_verify_created_vulns_with_host_and_service_verification(self, session, test_client):
201200 res = test_client.get(urljoin(self.url(workspace=command.workspace), 'activity_feed'))
202201 assert res.status_code == 200
203202 assert res.json == [{
204 u'_id': command.id,
205 u'command': command.command,
206 u'import_source': u'shell',
207 u'tool': command.tool,
208 u'user': command.user,
209 u'date': time.mktime(command.start_date.timetuple()) * 1000,
210 u'params': command.params,
211 u'hosts_count': 1,
212 u'services_count': 1,
213 u'vulnerabilities_count': 2,
214 u'criticalIssue': 1}
203 '_id': command.id,
204 'command': command.command,
205 'import_source': 'shell',
206 'tool': command.tool,
207 'user': command.user,
208 'date': time.mktime(command.start_date.timetuple()) * 1000,
209 'params': command.params,
210 'hosts_count': 1,
211 'services_count': 1,
212 'vulnerabilities_count': 2,
213 'criticalIssue': 1}
215214 ]
216215
217216 def test_multiple_commands_executed_with_same_objects_found(self, session, test_client):
269268 raw_first_command = list(filter(lambda comm: comm['_id'] == commands[0].id, res.json))
270269
271270 assert raw_first_command.pop() == {
272 u'_id': first_command.id,
273 u'command': first_command.command,
274 u'import_source': u'shell',
275 u'user': first_command.user,
276 u'date': time.mktime(first_command.start_date.timetuple()) * 1000,
277 u'params': first_command.params,
278 u'hosts_count': 1,
279 u'services_count': 0,
280 u'vulnerabilities_count': 1,
281 u'tool': first_command.tool,
282 u'criticalIssue': 0
271 '_id': first_command.id,
272 'command': first_command.command,
273 'import_source': 'shell',
274 'user': first_command.user,
275 'date': time.mktime(first_command.start_date.timetuple()) * 1000,
276 'params': first_command.params,
277 'hosts_count': 1,
278 'services_count': 0,
279 'vulnerabilities_count': 1,
280 'tool': first_command.tool,
281 'criticalIssue': 0
283282 }
284283
285284 for in_the_middle_command in in_the_middle_commands:
286285 raw_in_the_middle_command = list(filter(lambda comm: comm['_id'] == in_the_middle_command.id, res.json))
287 assert raw_in_the_middle_command.pop() == {u'_id': in_the_middle_command.id,
288 u'command': in_the_middle_command.command,
289 u'import_source': u'shell',
290 u'user': in_the_middle_command.user,
291 u'date': time.mktime(
286 assert raw_in_the_middle_command.pop() == {'_id': in_the_middle_command.id,
287 'command': in_the_middle_command.command,
288 'import_source': 'shell',
289 'user': in_the_middle_command.user,
290 'date': time.mktime(
292291 in_the_middle_command.start_date.timetuple()) * 1000,
293 u'params': in_the_middle_command.params,
294 u'hosts_count': 0,
295 u'tool': in_the_middle_command.tool,
296 u'services_count': 0,
297 u'vulnerabilities_count': 0,
298 u'criticalIssue': 0}
292 'params': in_the_middle_command.params,
293 'hosts_count': 0,
294 'tool': in_the_middle_command.tool,
295 'services_count': 0,
296 'vulnerabilities_count': 0,
297 'criticalIssue': 0}
299298
300299 # new command must create new service and vuln
301300 raw_last_command = list(filter(lambda comm: comm['_id'] == last_command.id, res.json))
302 assert raw_last_command.pop() == {u'_id': last_command.id,
303 u'command': last_command.command,
304 u'import_source': u'shell',
305 u'user': last_command.user,
306 u'date': time.mktime(last_command.start_date.timetuple()) * 1000,
307 u'params': last_command.params,
308 u'hosts_count': 0,
309 u'tool': last_command.tool,
310 u'services_count': 1,
311 u'vulnerabilities_count': 1,
312 u'criticalIssue': 0}
301 assert raw_last_command.pop() == {'_id': last_command.id,
302 'command': last_command.command,
303 'import_source': 'shell',
304 'user': last_command.user,
305 'date': time.mktime(last_command.start_date.timetuple()) * 1000,
306 'params': last_command.params,
307 'hosts_count': 0,
308 'tool': last_command.tool,
309 'services_count': 1,
310 'vulnerabilities_count': 1,
311 'criticalIssue': 0}
313312
314313 @pytest.mark.usefixtures('ignore_nplusone')
315314 def test_sub_second_command_returns_correct_duration_value(self, test_client):
369368 'hostname': 'mandarina',
370369 'ip': '192.168.20.53',
371370 'itime': 1511387720.048548,
372 'params': u'/home/lcubo/.faraday/report/airbnb/nessus_report_Remote.nessus',
371 'params': '/home/lcubo/.faraday/report/airbnb/nessus_report_Remote.nessus',
373372 'user': 'lcubo'
374373 }
375374
387386 'hostname': 'mandarina',
388387 'ip': '192.168.20.53',
389388 'itime': start_date.timestamp(),
390 'params': u'/home/lcubo/.faraday/report/airbnb/nessus_report_Remote.nessus',
389 'params': '/home/lcubo/.faraday/report/airbnb/nessus_report_Remote.nessus',
391390 'user': 'lcubo'
392391 }
393392
434433 'hostname': 'mandarina',
435434 'ip': '192.168.20.53',
436435 'itime': 1511387720000.048548,
437 'params': u'/home/lcubo/.faraday/report/airbnb/nessus_report_Remote.nessus',
436 'params': '/home/lcubo/.faraday/report/airbnb/nessus_report_Remote.nessus',
438437 'user': 'lcubo'
439438 }
440439
77 from faraday.server.api.modules.comments import CommentView
88 from faraday.server.models import Comment
99 from tests.factories import ServiceFactory
10 from tests.test_api_workspaced_base import ReadWriteAPITests
10 from tests.test_api_workspaced_base import ReadWriteAPITests, BulkDeleteTestsMixin
1111 from tests import factories
1212
1313
14 class TestCommentAPIGeneric(ReadWriteAPITests):
14 class TestCommentAPIGeneric(ReadWriteAPITests, BulkDeleteTestsMixin):
1515 model = Comment
1616 factory = factories.CommentFactory
1717 view_class = CommentView
6464 raw_comment = self._create_raw_comment('service', service.id)
6565 res = test_client.post(self.url(workspace=second_workspace), data=raw_comment)
6666 assert res.status_code == 400
67 assert res.json == {u'message': u"Can't comment object of another workspace"}
67 assert res.json == {'message': "Can't comment object of another workspace"}
6868
6969 def test_cannot_create_comment_of_inexistent_object(self, test_client, session):
7070 raw_comment = self._create_raw_comment('service', 456464556)
7171 res = test_client.post(self.url(workspace=self.workspace), data=raw_comment)
7272 assert res.status_code == 400
73 assert res.json == {u'message': u"Can't comment inexistent object"}
73 assert res.json == {'message': "Can't comment inexistent object"}
7474
7575 def test_create_unique_comment_for_plugins(self, session, test_client):
7676 """
121121 get_comments = test_client.get(self.url(workspace=workspace))
122122 expected = ['first', 'second', 'third', 'fourth']
123123 assert expected == [comment['text'] for comment in get_comments.json]
124
125 def test_bulk_delete_with_references(self, session, test_client):
126 previous_count = session.query(Comment).count()
127 comment_first = factories.CommentFactory.create(workspace=self.workspace, text='first')
128 comment_second = factories.CommentFactory.create(workspace=self.workspace, text='second', reply_to=comment_first)
129 _ = factories.CommentFactory.create(workspace=self.workspace, text='third', reply_to=comment_second)
130 comment_fourth = factories.CommentFactory.create(workspace=self.workspace, text='fourth')
131 session.commit()
132
133 data = {'ids': [comment_first.id, comment_fourth.id]}
134 res = test_client.delete(self.url(), data=data)
135
136 assert res.status_code == 200
137 assert res.json['deleted'] == 2
138 assert previous_count + 2 == session.query(Comment).count()
33 See the file 'doc/LICENSE' for the license information
44
55 '''
6 from urllib.parse import urljoin
67
78 import pytest
89
910 from tests import factories
1011 from tests.test_api_workspaced_base import (
1112 ReadWriteAPITests,
13 BulkUpdateTestsMixin,
14 BulkDeleteTestsMixin
1215 )
1316 from faraday.server.api.modules.credentials import CredentialView
1417 from faraday.server.models import Credential
1518 from tests.factories import HostFactory, ServiceFactory
1619
1720
18 class TestCredentialsAPIGeneric(ReadWriteAPITests):
21 class TestCredentialsAPIGeneric(ReadWriteAPITests, BulkUpdateTestsMixin, BulkDeleteTestsMixin):
1922 model = Credential
2023 factory = factories.CredentialFactory
2124 view_class = CredentialView
3134 assert res.status_code == 200
3235 assert 'rows' in res.json
3336 for vuln in res.json['rows']:
34 assert set([u'_id', u'id', u'key', u'value']) == set(vuln.keys())
37 assert {'_id', 'id', 'key', 'value'} == set(vuln.keys())
3538 object_properties = [
36 u'_id',
37 u'couchdbid',
38 u'description',
39 u'metadata',
40 u'name',
41 u'owner',
42 u'password',
43 u'username',
44 u'host_ip',
45 u'service_name',
46 u'target'
39 '_id',
40 'couchdbid',
41 'description',
42 'metadata',
43 'name',
44 'owner',
45 'password',
46 'username',
47 'host_ip',
48 'service_name',
49 'target'
4750 ]
4851 expected = set(object_properties)
4952 result = set(vuln['value'].keys())
104107 credential = self.factory.create(host=host, service=None,
105108 workspace=self.workspace)
106109 session.commit()
107 res = test_client.get(self.url(workspace=credential.workspace) + f'?host_id={credential.host.id}')
110 res = test_client.get(urljoin(self.url(workspace=credential.workspace), f'?host_id={credential.host.id}'))
108111 assert res.status_code == 200
109112 assert [cred['value']['parent'] for cred in res.json['rows']] == [credential.host.id]
110 assert [cred['value']['parent_type'] for cred in res.json['rows']] == [u'Host']
113 assert [cred['value']['parent_type'] for cred in res.json['rows']] == ['Host']
111114
112115 def test_get_credentials_for_a_service_backwards_compatibility(self, session, test_client):
113116 service = ServiceFactory.create()
114117 credential = self.factory.create(service=service, host=None, workspace=service.workspace)
115118 session.commit()
116 res = test_client.get(self.url(workspace=credential.workspace) + f'?service={credential.service.id}')
119 res = test_client.get(urljoin(self.url(workspace=credential.workspace), f'?service={credential.service.id}'))
117120 assert res.status_code == 200
118121 assert [cred['value']['parent'] for cred in res.json['rows']] == [credential.service.id]
119 assert [cred['value']['parent_type'] for cred in res.json['rows']] == [u'Service']
122 assert [cred['value']['parent_type'] for cred in res.json['rows']] == ['Service']
120123
121124 def _generate_raw_update_data(self, name, username, password, parent_id):
122125 return {
178181
179182 res = test_client.put(self.url(credential, workspace=credential.workspace), data=raw_data)
180183 assert res.status_code == 200
181 assert res.json['username'] == u'Username2'
182 assert res.json['password'] == u'Password3'
183 assert res.json['name'] == u'Name1'
184 assert res.json['username'] == 'Username2'
185 assert res.json['password'] == 'Password3'
186 assert res.json['name'] == 'Name1'
184187
185188 @pytest.mark.parametrize("parent_type, parent_factory", [
186189 ("Host", HostFactory),
254257 ]
255258
256259 # Desc order
257 response = test_client.get(self.url(workspace=second_workspace) + "?sort=target&sort_dir=desc")
260 response = test_client.get(urljoin(self.url(workspace=second_workspace), "?sort=target&sort_dir=desc"))
258261 assert response.status_code == 200
259262 assert sorted(credentials_target, reverse=True) == [v['value']['target'] for v in response.json['rows']]
260263
261264 # Asc order
262 response = test_client.get(self.url(workspace=second_workspace) + "?sort=target&sort_dir=asc")
265 response = test_client.get(urljoin(self.url(workspace=second_workspace), "?sort=target&sort_dir=asc"))
263266 assert response.status_code == 200
264267 assert sorted(credentials_target) == [v['value']['target'] for v in response.json['rows']]
00 import pytest
11
22 from tests.factories import CustomFieldsSchemaFactory
3 from tests.test_api_non_workspaced_base import ReadWriteAPITests
3 from tests.test_api_non_workspaced_base import ReadWriteAPITests, BulkDeleteTestsMixin
44
55 from faraday.server.api.modules.custom_fields import CustomFieldsSchemaView
66 from faraday.server.models import (
99
1010
1111 @pytest.mark.usefixtures('logged_user')
12 class TestVulnerabilityCustomFields(ReadWriteAPITests):
12 class TestVulnerabilityCustomFields(ReadWriteAPITests, BulkDeleteTestsMixin):
1313 model = CustomFieldsSchema
1414 factory = CustomFieldsSchemaFactory
1515 api_endpoint = 'custom_fields_schema'
1616 # unique_fields = ['ip']
1717 # update_fields = ['ip', 'description', 'os']
1818 view_class = CustomFieldsSchemaView
19 patchable_fields = ['field_name']
19 patchable_fields = ['field_display_name']
2020
2121 def test_custom_fields_data(self, session, test_client):
2222 add_text_field = CustomFieldsSchemaFactory.create(
3131
3232 res = test_client.get(self.url())
3333 assert res.status_code == 200
34 assert {u'table_name': u'vulnerability', u'id': add_text_field.id, u'field_type': u'text',
35 u'field_name': u'cvss', u'field_display_name': u'CVSS', u'field_metadata': None,
36 u'field_order': 1} in res.json
34 assert {'table_name': 'vulnerability', 'id': add_text_field.id, 'field_type': 'text',
35 'field_name': 'cvss', 'field_display_name': 'CVSS', 'field_metadata': None,
36 'field_order': 1} in res.json
3737
3838 def test_custom_fields_field_name_cant_be_changed(self, session, test_client):
3939 add_text_field = CustomFieldsSchemaFactory.create(
4747 session.commit()
4848
4949 data = {
50 u'field_name': u'cvss 2',
51 u'field_type': 'int',
52 u'table_name': 'sarasa',
53 u'field_display_name': u'CVSS new',
54 u'field_order': 1
50 'field_name': 'cvss 2',
51 'field_type': 'int',
52 'table_name': 'sarasa',
53 'field_display_name': 'CVSS new',
54 'field_order': 1
5555 }
5656 res = test_client.put(self.url(add_text_field.id), data=data)
5757 assert res.status_code == 200
7777
7878 res = test_client.get(self.url())
7979 assert res.status_code == 200
80 assert {u'table_name': u'vulnerability', u'id': add_choice_field.id, u'field_type': u'choice',
81 u'field_name': u'gender', u'field_display_name': u'Gender', u'field_metadata': "['Male', 'Female']",
82 u'field_order': 1} in res.json
80 assert {'table_name': 'vulnerability', 'id': add_choice_field.id, 'field_type': 'choice',
81 'field_name': 'gender', 'field_display_name': 'Gender', 'field_metadata': "['Male', 'Female']",
82 'field_order': 1} in res.json
55 '''
66 import operator
77 from io import BytesIO
8 from posixpath import join as urljoin
8 from posixpath import join
99
1010 import pytz
1111
12 from urllib.parse import urlencode
12 from urllib.parse import urlencode, urljoin
1313 from random import choice
1414 from sqlalchemy.orm.util import was_deleted
1515 from hypothesis import given, strategies as st
2121 API_PREFIX,
2222 ReadWriteAPITests,
2323 PaginationTestsMixin,
24 BulkUpdateTestsMixin,
25 BulkDeleteTestsMixin
2426 )
2527 from faraday.server.models import db, Host, Hostname
2628 from faraday.server.api.modules.hosts import HostsView
27 from tests.factories import HostFactory, EmptyCommandFactory, WorkspaceFactory
29 from tests.factories import HostFactory, EmptyCommandFactory, WorkspaceFactory, HostnameFactory
2830
2931 HOSTS_COUNT = 5
3032 SERVICE_COUNT = [10, 5] # 10 services to the first host, 5 to the second
6062
6163 def url(self, host=None, workspace=None):
6264 workspace = workspace or self.workspace
63 url = API_PREFIX + workspace.name + '/hosts'
65 url = join(API_PREFIX + workspace.name, 'hosts')
6466 if host is not None:
65 url += '/' + str(host.id)
67 url = join(url, str(host.id))
6668 return url
6769
6870 def services_url(self, host, workspace=None):
69 return self.url(host, workspace) + '/services'
71 return join(self.url(host, workspace), 'services')
7072
7173 def compare_results(self, hosts, response):
7274 """
7375 Compare is the hosts in response are the same that in hosts.
7476 It only compares the IDs of each one, not other fields"""
75 hosts_in_list = set(host.id for host in hosts)
76 hosts_in_response = set(host['id'] for host in response.json['rows'])
77 hosts_in_list = {host.id for host in hosts}
78 hosts_in_response = {host['id'] for host in response.json['rows']}
7779 assert hosts_in_list == hosts_in_response
7880
7981 def test_list_retrieves_all_items_from_workspace(self, test_client,
278280 host_factory.create_batch(5, workspace=second_workspace, os='Unix')
279281
280282 session.commit()
281 url = self.url() + '?os=Unix'
283 url = urljoin(self.url(), '?os=Unix')
282284 res = test_client.get(url)
283285 assert res.status_code == 200
284286 self.compare_results(hosts, res)
296298 host_factory.create_batch(5, workspace=second_workspace, os='Unix')
297299
298300 session.commit()
299 res = test_client.get(urljoin(self.url(), 'filter?q={"filters":[{"name": "os", "op":"eq", "val":"Unix"}]}'))
301 res = test_client.get(join(self.url(), 'filter?q={"filters":[{"name": "os", "op":"eq", "val":"Unix"}]}'))
300302 assert res.status_code == 200
301303 self.compare_results(hosts, res)
302304
310312 host_factory.create_batch(5, workspace=second_workspace, os='Unix')
311313
312314 session.commit()
313 res = test_client.get(urljoin(self.url(), 'filter?q={"filters":[{"name": "os", "op":"eq", "val":"Unix"}],'
315 res = test_client.get(join(self.url(), 'filter?q={"filters":[{"name": "os", "op":"eq", "val":"Unix"}],'
314316 '"offset":0, "limit":20}'))
315317 assert res.status_code == 200
316318 assert res.json['count'] == 30
320322 host_factory.create_batch(10, workspace=workspace, os='Unix')
321323 host_factory.create_batch(1, workspace=workspace, os='unix')
322324 session.commit()
323 res = test_client.get(urljoin(self.url(), 'filter?q={"filters":[{"name": "os", "op": "like", "val": "%nix"}], '
325 res = test_client.get(join(self.url(), 'filter?q={"filters":[{"name": "os", "op": "like", "val": "%nix"}], '
324326 '"group_by":[{"field": "os"}], "order_by":[{"field": "os", "direction": "desc"}]}'))
325327 assert res.status_code == 200
326328 assert len(res.json['rows']) == 2
345347 host_factory.create_batch(5, workspace=second_workspace, os='Unix')
346348
347349 session.commit()
348 res = test_client.get(self.url() + '?os__like=Unix %')
350 res = test_client.get(urljoin(self.url(), '?os__like=Unix %'))
349351 assert res.status_code == 200
350352 self.compare_results(hosts, res)
351353
352 res = test_client.get(self.url() + '?os__ilike=Unix %')
354 res = test_client.get(urljoin(self.url(), '?os__ilike=Unix %'))
353355 assert res.status_code == 200
354356 self.compare_results(hosts + [case_insensitive_host], res)
355357
371373 host_factory.create_batch(5, workspace=second_workspace, os='Unix')
372374
373375 session.commit()
374 res = test_client.get(urljoin(
376 res = test_client.get(join(
375377 self.url(),
376378 'filter?q={"filters":[{"name": "os", "op":"like", "val":"Unix %"}]}'
377379 )
379381 assert res.status_code == 200
380382 self.compare_results(hosts, res)
381383
382 res = test_client.get(urljoin(
384 res = test_client.get(join(
383385 self.url(),
384386 'filter?q={"filters":[{"name": "os", "op":"ilike", "val":"Unix %"}]}'
385387 )
397399 host_factory.create_batch(5, workspace=workspace)
398400
399401 session.commit()
400 res = test_client.get(self.url() + '?service=IRC')
401 assert res.status_code == 200
402 shown_hosts_ids = set(obj['id'] for obj in res.json['rows'])
403 expected_host_ids = set(host.id for host in hosts)
402 res = test_client.get(urljoin(self.url(), '?service=IRC'))
403 assert res.status_code == 200
404 shown_hosts_ids = {obj['id'] for obj in res.json['rows']}
405 expected_host_ids = {host.id for host in hosts}
404406 assert shown_hosts_ids == expected_host_ids
405407
406408 @pytest.mark.usefixtures('ignore_nplusone')
416418 session.commit()
417419
418420 res = test_client.get(
419 urljoin(
421 join(
420422 self.url(),
421423 'filter?q={"filters":[{"name": "services__name", "op":"any", "val":"IRC"}]}'
422424 )
423425 )
424426 assert res.status_code == 200
425 shown_hosts_ids = set(obj['id'] for obj in res.json['rows'])
426 expected_host_ids = set(host.id for host in hosts)
427 shown_hosts_ids = {obj['id'] for obj in res.json['rows']}
428 expected_host_ids = {host.id for host in hosts}
427429 assert shown_hosts_ids == expected_host_ids
428430
429431 def test_filter_by_service_port(self, test_client, session, workspace,
435437 host_factory.create_batch(5, workspace=workspace)
436438
437439 session.commit()
438 res = test_client.get(self.url() + '?port=25')
439 assert res.status_code == 200
440 shown_hosts_ids = set(obj['id'] for obj in res.json['rows'])
441 expected_host_ids = set(host.id for host in hosts)
440 res = test_client.get(urljoin(self.url(), '?port=25'))
441 assert res.status_code == 200
442 shown_hosts_ids = {obj['id'] for obj in res.json['rows']}
443 expected_host_ids = {host.id for host in hosts}
442444 assert shown_hosts_ids == expected_host_ids
443445
444446 @pytest.mark.usefixtures('ignore_nplusone')
452454
453455 session.commit()
454456 res = test_client.get(
455 urljoin(
457 join(
456458 self.url(),
457459 'filter?q={"filters":[{"name": "services__port", "op":"any", "val":"25"}]}'
458460 )
459461 )
460462 assert res.status_code == 200
461 shown_hosts_ids = set(obj['id'] for obj in res.json['rows'])
462 expected_host_ids = set(host.id for host in hosts)
463 shown_hosts_ids = {obj['id'] for obj in res.json['rows']}
464 expected_host_ids = {host.id for host in hosts}
463465 assert shown_hosts_ids == expected_host_ids
464466
465467 @pytest.mark.usefixtures('ignore_nplusone')
478480 session.commit()
479481
480482 res = test_client.get(
481 urljoin(
483 join(
482484 self.url(),
483485 f'filter?q={{"filters":[{{"name": "ip", "op":"eq", "val":"{host.ip}"}}]}}'
484486 )
504506 host_factory.create_batch(5, workspace=workspace)
505507
506508 session.commit()
507 res = test_client.get(self.url() + '?port=invalid_port')
509 res = test_client.get(urljoin(self.url(), '?port=invalid_port'))
508510 assert res.status_code == 200
509511 assert res.json['count'] == 0
510512
519521
520522 session.commit()
521523 res = test_client.get(
522 urljoin(
524 join(
523525 self.url(),
524526 'filter?q={"filters":[{"name": "services__port", "op":"any", "val":"sarasa"}]}'
525527 )
528530
529531 def test_filter_restless_by_invalid_field(self, test_client):
530532 res = test_client.get(
531 urljoin(
533 join(
532534 self.url(),
533535 'filter?q={"filters":[{"name": "severity", "op":"any", "val":"sarasa"}]}'
534536 )
537539
538540 @pytest.mark.usefixtures('ignore_nplusone')
539541 def test_filter_restless_with_no_q_param(self, test_client, session, workspace, host_factory):
540 res = test_client.get(urljoin(self.url(), 'filter'))
542 res = test_client.get(join(self.url(), 'filter'))
541543 assert res.status_code == 200
542544 assert len(res.json['rows']) == HOSTS_COUNT
543545
544546 @pytest.mark.usefixtures('ignore_nplusone')
545547 def test_filter_restless_with_empty_q_param(self, test_client, session, workspace, host_factory):
546 res = test_client.get(urljoin(self.url(), 'filter?q'))
548 res = test_client.get(join(self.url(), 'filter?q'))
547549 assert res.status_code == 400
548550
549551 def test_search_ip(self, test_client, session, workspace, host_factory):
550552 host = host_factory.create(ip="longname",
551553 workspace=workspace)
552554 session.commit()
553 res = test_client.get(self.url() + '?search=ONGNAM')
555 res = test_client.get(urljoin(self.url(), '?search=ONGNAM'))
554556 assert res.status_code == 200
555557 assert len(res.json['rows']) == 1
556558 assert res.json['rows'][0]['id'] == host.id
563565 service_factory.create(host=host, name="GOPHER 5",
564566 workspace=workspace)
565567 session.commit()
566 res = test_client.get(self.url() + '?search=gopher')
567 assert res.status_code == 200
568 shown_hosts_ids = set(obj['id'] for obj in res.json['rows'])
569 expected_host_ids = set(host.id for host in expected_hosts)
568 res = test_client.get(urljoin(self.url(), '?search=gopher'))
569 assert res.status_code == 200
570 shown_hosts_ids = {obj['id'] for obj in res.json['rows']}
571 expected_host_ids = {host.id for host in expected_hosts}
570572 assert shown_hosts_ids == expected_host_ids
571573
572574 @pytest.mark.usefixtures('host_with_hostnames')
575577 for host in expected_hosts:
576578 host.set_hostnames(['staging.twitter.com'])
577579 session.commit()
578 res = test_client.get(self.url() + '?search=twitter')
579 assert res.status_code == 200
580 shown_hosts_ids = set(obj['id'] for obj in res.json['rows'])
581 expected_host_ids = set(host.id for host in expected_hosts)
580 res = test_client.get(urljoin(self.url(), '?search=twitter'))
581 assert res.status_code == 200
582 shown_hosts_ids = {obj['id'] for obj in res.json['rows']}
583 expected_host_ids = {host.id for host in expected_hosts}
582584 assert shown_hosts_ids == expected_host_ids
583585
584586 def test_host_with_open_vuln_count_verification(self, test_client, session,
613615 vulnerability_factory.create(service=service, host=None, workspace=workspace)
614616 session.commit()
615617
616 res = test_client.get(urljoin(self.url(host), 'services'))
618 res = test_client.get(join(self.url(host), 'services'))
617619 assert res.status_code == 200
618620 assert res.json[0]['vulns'] == 1
619621
648650 }
649651 res = test_client.put(self.url(host_with_hostnames), data=data)
650652 assert res.status_code == 200
651 expected = set(["other.com", "test.com"])
653 expected = {"other.com", "test.com"}
652654 assert set(res.json['hostnames']) == expected
653 assert set(hn.name for hn in host_with_hostnames.hostnames) == expected
655 assert {hn.name for hn in host_with_hostnames.hostnames} == expected
654656
655657 def test_create_host_with_default_gateway(self, test_client):
656658 raw_data = {
700702 assert res.status_code == 200
701703 updated_host = Host.query.filter_by(id=host.id).first()
702704 assert res.json == {
703 u'_id': host.id,
704 u'type': u'Host',
705 u'_rev': u'',
706 u'credentials': 0,
707 u'default_gateway': '',
708 u'description': u'',
709 u'hostnames': [],
710 u'id': host.id,
711 u'ip': u'10.31.112.21',
712 u'mac': '',
713 u'metadata': {
714 u'command_id': None,
715 u'create_time': pytz.UTC.localize(updated_host.create_date).isoformat(),
716 u'creator': u'',
717 u'owner': host.creator.username,
718 u'update_action': 0,
719 u'update_controller_action': u'',
720 u'update_time': pytz.UTC.localize(updated_host.update_date).isoformat(),
721 u'update_user': None},
722 u'name': u'10.31.112.21',
723 u'os': u'Microsoft Windows Server 2008 R2 Standard Service Pack 1',
724 u'owned': False,
725 u'owner': host.creator.username,
726 u'services': 0,
727 u'service_summaries': [],
728 u'vulns': 0,
729 u"versions": [],
730 u'important': False,
731 u'severity_counts': {
732 u'critical': None,
733 u'high': None,
734 u'host_id': host.id,
735 u'info': None,
736 u'med': None,
737 u'low': None,
738 u'total': None,
739 u'unclassified': None
705 '_id': host.id,
706 'type': 'Host',
707 '_rev': '',
708 'credentials': 0,
709 'default_gateway': '',
710 'description': '',
711 'hostnames': [],
712 'id': host.id,
713 'ip': '10.31.112.21',
714 'mac': '',
715 'metadata': {
716 'command_id': None,
717 'create_time': pytz.UTC.localize(updated_host.create_date).isoformat(),
718 'creator': '',
719 'owner': host.creator.username,
720 'update_action': 0,
721 'update_controller_action': '',
722 'update_time': pytz.UTC.localize(updated_host.update_date).isoformat(),
723 'update_user': None},
724 'name': '10.31.112.21',
725 'os': 'Microsoft Windows Server 2008 R2 Standard Service Pack 1',
726 'owned': False,
727 'owner': host.creator.username,
728 'services': 0,
729 'service_summaries': [],
730 'vulns': 0,
731 "versions": [],
732 'important': False,
733 'severity_counts': {
734 'critical': None,
735 'high': None,
736 'host_id': host.id,
737 'info': None,
738 'med': None,
739 'low': None,
740 'total': None,
741 'unclassified': None
740742 }
741743 }
742744
761763 assert res.json['hosts_with_errors'] == 0
762764 assert session.query(Host).filter_by(description="test_host").count() == expected_created_hosts
763765
764 @pytest.mark.skip("This was a v2 test, will be reimplemented")
765766 def test_bulk_delete_hosts(self, test_client, session):
766 ws = WorkspaceFactory.create(name="abc")
767 host_1 = HostFactory.create(workspace=ws)
768 host_2 = HostFactory.create(workspace=ws)
767 host_1 = HostFactory.create(workspace=self.workspace)
768 host_2 = HostFactory.create(workspace=self.workspace)
769769 session.commit()
770770 hosts_ids = [host_1.id, host_2.id]
771 request_data = {'hosts_ids': hosts_ids}
772
773 delete_response = test_client.delete(f'/v3/ws/{ws.name}/hosts/bulk_delete', data=request_data)
774
775 deleted_hosts = delete_response.json['deleted_hosts']
771 request_data = {'ids': hosts_ids}
772
773 delete_response = test_client.delete(self.url(), data=request_data)
774
775 deleted_hosts = delete_response.json['deleted']
776776 host_count_after_delete = db.session.query(Host).filter(
777777 Host.id.in_(hosts_ids),
778 Host.workspace_id == ws.id).count()
778 Host.workspace_id == self.workspace.id).count()
779779
780780 assert delete_response.status_code == 200
781781 assert deleted_hosts == len(hosts_ids)
782782 assert host_count_after_delete == 0
783783
784 @pytest.mark.skip("This was a v2 test, will be reimplemented")
785784 def test_bulk_delete_hosts_without_hosts_ids(self, test_client):
786 ws = WorkspaceFactory.create(name="abc")
787785 request_data = {'hosts_ids': []}
788786
789 delete_response = test_client.delete(f'/v3/ws/{ws.name}/hosts/bulk_delete', data=request_data)
787 delete_response = test_client.delete(self.url(), data=request_data)
790788
791789 assert delete_response.status_code == 400
792790
793 @pytest.mark.skip("This was a v2 test, will be reimplemented")
794791 def test_bulk_delete_hosts_from_another_workspace(self, test_client, session):
795792 workspace_1 = WorkspaceFactory.create(name='workspace_1')
796793 host_of_ws_1 = HostFactory.create(workspace=workspace_1)
799796 session.commit()
800797
801798 # Try to delete workspace_2's host from workspace_1
802 request_data = {'hosts_ids': [host_of_ws_2.id]}
803 url = f'/v3/ws/{workspace_1.name}/hosts/bulk_delete'
799 request_data = {'ids': [host_of_ws_2.id]}
800 url = f'/v3/ws/{workspace_1.name}/hosts'
804801 delete_response = test_client.delete(url, data=request_data)
805802
806 assert delete_response.json['deleted_hosts'] == 0
807
808 @pytest.mark.skip("This was a v2 test, will be reimplemented")
803 assert delete_response.status_code == 200
804 assert delete_response.json['deleted'] == 0
805
809806 def test_bulk_delete_hosts_invalid_characters_in_request(self, test_client):
810807 ws = WorkspaceFactory.create(name="abc")
811 request_data = {'hosts_ids': [-1, 'test']}
812 delete_response = test_client.delete(f'/v3/ws/{ws.name}/hosts/bulk_delete', data=request_data)
813
814 assert delete_response.json['deleted_hosts'] == 0
815
816 @pytest.mark.skip("This was a v2 test, will be reimplemented")
808 request_data = {'ids': [-1, 'test']}
809 delete_response = test_client.delete(f'/v3/ws/{ws.name}/hosts', data=request_data)
810
811 assert delete_response.json['deleted'] == 0
812
817813 def test_bulk_delete_hosts_wrong_content_type(self, test_client, session):
818814 ws = WorkspaceFactory.create(name="abc")
819815 host_1 = HostFactory.create(workspace=ws)
821817 session.commit()
822818 hosts_ids = [host_1.id, host_2.id]
823819
824 request_data = {'hosts_ids': hosts_ids}
820 request_data = {'ids': hosts_ids}
825821 headers = [('content-type', 'text/xml')]
826822
827823 delete_response = test_client.delete(
828 f'/v3/ws/{ws.name}/hosts/bulk_delete',
824 f'/v3/ws/{ws.name}/hosts',
829825 data=request_data,
830826 headers=headers)
831827
832828 assert delete_response.status_code == 400
833829
834
835 class TestHostAPIGeneric(ReadWriteAPITests, PaginationTestsMixin):
830 def test_bulk_delete_with_references(self, test_client, session, workspace, host_factory, vulnerability_factory,
831 service_factory, credential_factory):
832 host_1 = host_factory.create(workspace=workspace)
833 service_factory.create(host=host_1, workspace=workspace)
834 vulnerability_factory.create(service=None, host=host_1, workspace=workspace)
835 host_1.hostnames.append(HostnameFactory.create(name='pepe1', workspace=workspace, host=host_1))
836 credential_factory.create(workspace=workspace, host=host_1)
837
838 host_2 = host_factory.create(workspace=workspace)
839 service_factory.create(host=host_2, workspace=workspace)
840 vulnerability_factory.create(service=None, host=host_2, workspace=workspace)
841 host_1.hostnames.append(HostnameFactory.create(name='pepe2', workspace=workspace, host=host_2))
842 credential_factory.create(workspace=workspace, host=host_2)
843
844 session.commit()
845
846 hosts_ids = [host_1.id, host_2.id]
847 request_data = {'ids': hosts_ids}
848 url = f'/v3/ws/{workspace.name}/hosts'
849 delete_response = test_client.delete(url, data=request_data)
850
851 assert delete_response.status_code == 200
852 assert delete_response.json['deleted'] == 2
853
854
855 class TestHostAPIGeneric(ReadWriteAPITests, PaginationTestsMixin, BulkUpdateTestsMixin, BulkDeleteTestsMixin):
836856 model = Host
837857 factory = factories.HostFactory
838858 api_endpoint = 'hosts'
850870 expected_ids = [host.id for host in
851871 sorted(Host.query.all(),
852872 key=operator.attrgetter('description'))]
853 res = test_client.get(self.url() + '?sort=description&sort_dir=asc')
873 res = test_client.get(urljoin(self.url(), '?sort=description&sort_dir=asc'))
854874 assert res.status_code == 200
855875 assert [host['_id'] for host in res.json['data']] == expected_ids
856876
857877 expected_ids.reverse() # In place list reverse
858 res = test_client.get(self.url() + '?sort=description&sort_dir=desc')
878 res = test_client.get(urljoin(self.url(), '?sort=description&sort_dir=desc'))
859879 assert res.status_code == 200
860880 assert [host['_id'] for host in res.json['data']] == expected_ids
861881
870890 session.flush()
871891 expected_ids.append(host.id)
872892 session.commit()
873 res = test_client.get(self.url(workspace=second_workspace)
874 + '?sort=services&sort_dir=asc')
893 res = test_client.get(urljoin(self.url(workspace=second_workspace),
894 '?sort=services&sort_dir=asc'))
875895 assert res.status_code == 200
876896 assert [h['_id'] for h in res.json['data']] == expected_ids
877897
892912 session.add(host)
893913 session.commit()
894914 expected.append(host) # Put it on the end
895 res = test_client.get(self.url(workspace=second_workspace)
896 + '?sort=metadata.update_time&sort_dir=asc')
915 res = test_client.get(urljoin(self.url(workspace=second_workspace),
916 '?sort=metadata.update_time&sort_dir=asc'))
897917 assert res.status_code == 200, res.data
898918 assert [h['_id'] for h in res.json['data']] == [h.id for h in expected]
899919
921941 command = EmptyCommandFactory.create()
922942 session.commit()
923943 assert len(command.command_objects) == 0
924 url = self.url(workspace=command.workspace) + '?' + urlencode({'command_id': command.id})
944 url = urljoin(self.url(workspace=command.workspace), f"?{urlencode({'command_id': command.id})}")
925945
926946 res = test_client.post(url, data={
927947 "ip": "127.0.0.1",
939959 new_workspace = WorkspaceFactory.create()
940960 session.commit()
941961 assert len(command.command_objects) == 0
942 url = self.url(workspace=new_workspace) + '?' + urlencode({'command_id': command.id})
962 url = urljoin(self.url(workspace=new_workspace), f"?{urlencode({'command_id': command.id})}")
943963
944964 res = test_client.post(url, data={
945965 "ip": "127.0.0.1",
947967 })
948968
949969 assert res.status_code == 400
950 assert res.json == {u'message': u'Command not found.'}
970 assert res.json == {'message': 'Command not found.'}
951971 assert len(command.command_objects) == 0
952972
953973 def test_service_summaries(self, test_client, session, service_factory):
11021122 index_in_response_hosts = response_hosts.index(host)
11031123
11041124 assert index_in_hosts_ids == index_in_response_hosts
1125
1126 @pytest.mark.usefixtures('ignore_nplusone')
1127 def test_bulk_update_host_with_hostnames(self, test_client, session,
1128 host_with_hostnames):
1129 session.commit()
1130 data = {
1131 "ids": [host_with_hostnames.id, self.first_object.id],
1132 "hostnames": ["other.com", "test.com"],
1133 }
1134 res = test_client.patch(self.url(), data=data)
1135 assert res.status_code == 200
1136 assert res.json["updated"] == 2
1137 expected = {"other.com", "test.com"}
1138 assert {hn.name for hn in host_with_hostnames.hostnames} == expected
1139 assert {hn.name for hn in self.first_object.hostnames} == expected
1140
1141 @pytest.mark.usefixtures('ignore_nplusone')
1142 def test_bulk_update_host_without_hostnames(self, test_client, session,
1143 host_with_hostnames):
1144 session.commit()
1145 expected = {hn.name for hn in host_with_hostnames.hostnames}
1146 data = {
1147 "ids": [host_with_hostnames.id],
1148 "os": "NotAnOS"
1149 }
1150 res = test_client.patch(self.url(), data=data)
1151 assert res.status_code == 200
1152 assert res.json["updated"] == 1
1153 assert {hn.name for hn in host_with_hostnames.hostnames} == expected
11051154
11061155
11071156 def host_json():
0 # -*- coding: utf8 -*-
10 '''
21 Faraday Penetration Test IDE
32 Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/)
1211 from hypothesis import given, strategies as st
1312
1413 from tests import factories
15 from tests.test_api_non_workspaced_base import ReadWriteAPITests, API_PREFIX
14 from tests.test_api_non_workspaced_base import (
15 ReadWriteAPITests,
16 API_PREFIX
17 )
1618 from faraday.server.models import (
1719 License,
1820 )
3234 model = License
3335 factory = factories.LicenseFactory
3436 api_endpoint = 'licenses'
35 patchable_fields = ["products"]
37 view_class = LicenseView
38 patchable_fields = ["product"]
3639
3740 # @pytest.mark.skip(reason="Not a license actually test")
3841 def test_envelope_list(self, test_client, app):
0
10 import pytest
21 from flask_security.utils import hash_password
32 from itsdangerous import TimedJSONWebSignatureSerializer
0 # -*- coding: utf8 -*-
10 '''
21 Faraday Penetration Test IDE
32 Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/)
43 See the file 'doc/LICENSE' for the license information
54
65 '''
7 from builtins import str
8
6 from posixpath import join
97 """Generic tests for APIs NOT prefixed with a workspace_name"""
108
119 import pytest
4442 if obj is not None:
4543 id_ = str(getattr(obj, self.lookup_field)) if isinstance(
4644 obj, self.model) else str(obj)
47 url += u'/' + id_
45 url = join(url, id_)
4846 return url
4947
5048
6866 assert res.status_code == 200
6967 assert isinstance(res.json, dict)
7068
71 @pytest.mark.parametrize('object_id', [123456789, -1, 'xxx', u'áá'])
69 @pytest.mark.parametrize('object_id', [123456789, -1, 'xxx', 'áá'])
7270 def test_404_when_retrieving_unexistent_object(self, test_client,
7371 object_id):
7472 url = self.url(object_id)
144142
145143
146144 @pytest.mark.usefixtures('logged_user')
145 class BulkUpdateTestsMixin:
146
147 @staticmethod
148 def control_data(test_suite, data: dict) -> dict:
149 return UpdateTestsMixin.control_data(test_suite, data)
150
151 def test_bulk_update_an_object(self, test_client, logged_user):
152 all_objs = self.model.query.all()
153 all_objs_id = [obj.__getattribute__(self.view_class.lookup_field) for obj in self.model.query.all()]
154 all_objs, all_objs_id = all_objs[:-1], all_objs_id[:-1]
155
156 data = self.factory.build_dict()
157 data = BulkUpdateTestsMixin.control_data(self, data)
158
159 res = test_client.patch(self.url(), data={})
160 assert res.status_code == 400
161 data["ids"] = all_objs_id
162 res = test_client.patch(self.url(), data=data)
163
164 assert res.status_code == 200, (res.status_code, res.json)
165 assert self.model.query.count() == OBJECT_COUNT
166 assert res.json['updated'] == len(all_objs)
167 for obj in self.model.query.all():
168 if getattr(obj, self.view_class.lookup_field) not in all_objs_id:
169 assert any(
170 [
171 data[updated_field] != getattr(obj, updated_field)
172 for updated_field in data if updated_field != 'ids'
173 ]
174 )
175 else:
176 assert all(
177 [
178 data[updated_field] == getattr(obj, updated_field)
179 for updated_field in data if updated_field != 'ids'
180 ]
181 )
182
183 def test_bulk_update_fails_with_existing(self, test_client, session):
184 for unique_field in self.unique_fields:
185 data = self.factory.build_dict()
186 data[unique_field] = getattr(self.objects[3], unique_field)
187 data["ids"] = [getattr(self.objects[i], self.view_class.lookup_field) for i in range(0, 2)],
188 res = test_client.patch(self.url(), data=data)
189 assert res.status_code == 400
190 assert self.model.query.count() == OBJECT_COUNT
191 assert res.json['updated'] == 0
192
193 def test_patch_bulk_update_an_object_does_not_fail_with_partial_data(self, test_client, logged_user):
194 """To do this the user should use a PATCH request"""
195 all_objs_id = [obj.__getattribute__(self.view_class.lookup_field) for obj in self.model.query.all()]
196 res = test_client.patch(self.url(), data={"ids": all_objs_id})
197 assert res.status_code == 200, (res.status_code, res.json)
198 assert res.json['updated'] == 0
199
200 def test_bulk_update_invalid_ids(self, test_client):
201 data = self.factory.build_dict()
202 data = BulkUpdateTestsMixin.control_data(self, data)
203 data['ids'] = [-1, 'test']
204 res = test_client.patch(self.url(), data=data)
205 assert res.status_code == 200
206 assert res.json['updated'] == 0
207 data['ids'] = [-1, 'test', self.first_object.__getattribute__(self.view_class.lookup_field)]
208 res = test_client.patch(self.url(), data=data)
209 assert res.status_code == 200
210 assert res.json['updated'] == 1
211
212 def test_bulk_update_wrong_content_type(self, test_client):
213 all_objs = self.model.query.all()
214 all_objs_id = [obj.__getattribute__(self.view_class.lookup_field) for obj in all_objs]
215
216 request_data = {'ids': all_objs_id}
217 headers = [('content-type', 'text/xml')]
218
219 res = test_client.patch(self.url(), data=request_data, headers=headers)
220 assert res.status_code == 400
221
222
223 @pytest.mark.usefixtures('logged_user')
147224 class DeleteTestsMixin:
148225
149226 def test_delete(self, test_client, logged_user):
152229 assert was_deleted(self.first_object)
153230 assert self.model.query.count() == OBJECT_COUNT - 1
154231
155 @pytest.mark.parametrize('object_id', [12300, -1, 'xxx', u'áá'])
232 @pytest.mark.parametrize('object_id', [12300, -1, 'xxx', 'áá'])
156233 def test_delete_non_existent_raises_404(self, test_client,
157234 object_id):
158235 res = test_client.delete(self.url(object_id))
159236 assert res.status_code == 404 # No content
160237 assert self.model.query.count() == OBJECT_COUNT
238
239
240 @pytest.mark.usefixtures('logged_user')
241 class BulkDeleteTestsMixin:
242
243 @pytest.mark.usefixtures('ignore_nplusone')
244 def test_bulk_delete(self, test_client):
245
246 all_objs = self.model.query.all()
247 all_objs_id = [obj.__getattribute__(self.view_class.lookup_field) for obj in all_objs]
248 ignored_obj = all_objs[-1]
249 all_objs, all_objs_id = all_objs[:-1], all_objs_id[:-1]
250
251 res = test_client.delete(self.url(), data={})
252 assert res.status_code == 400
253 data = {"ids": all_objs_id}
254 res = test_client.delete(self.url(), data=data)
255 assert res.status_code == 200
256 assert all([was_deleted(obj) for obj in all_objs])
257 assert res.json['deleted'] == len(all_objs)
258 assert not was_deleted(ignored_obj)
259 assert self.model.query.count() == 1
260
261 def test_bulk_delete_invalid_ids(self, test_client):
262 request_data = {'ids': [-1, 'test']}
263 count = self.model.query.count()
264 res = test_client.delete(self.url(), data=request_data)
265 assert res.status_code == 200
266 assert res.json['deleted'] == 0
267 assert self.model.query.count() == count
268
269 def test_bulk_delete_wrong_content_type(self, test_client):
270 all_objs = self.model.query.all()
271 all_objs_id = [obj.__getattribute__(self.view_class.lookup_field) for obj in all_objs]
272 count = self.model.query.count()
273
274 request_data = {'ids': all_objs_id}
275 headers = [('content-type', 'text/xml')]
276
277 res = test_client.delete(self.url(), data=request_data, headers=headers)
278 assert res.status_code == 400
279 assert self.model.query.count() == count
280 assert all([not was_deleted(obj) for obj in all_objs])
161281
162282
163283 class ReadWriteTestsMixin(ListTestsMixin,
0 # -*- coding: utf8 -*-
10 '''
21 Faraday Penetration Test IDE
32 Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/)
1211 try:
1312 from urllib import urlencode
1413 except ImportError as e:
15 from urllib.parse import urlencode
14 from urllib.parse import urlencode, urljoin
1615
1716
1817 def with_0_and_n_objects(n=10):
4544 self.view_class.page_number_parameter_name] = page_number
4645 if per_page is not None:
4746 parameters[self.view_class.per_page_parameter_name] = per_page
48 return self.url() + '?' + urlencode(parameters)
47 return urljoin(self.url(), f'?{urlencode(parameters)}')
4948
5049 @pytest.mark.parametrize("page_number", [None, 1, 2])
5150 @pytest.mark.usefixtures('pagination_test_logic')
7776 self.create_many_objects(session, object_count)
7877 res = test_client.get(self.page_url(-1, 10))
7978 assert res.status_code == 200
80 assert res.json == {u'data': []}
79 assert res.json == {'data': []}
8180
8281 @pytest.mark.usefixtures('pagination_test_logic')
8382 @pytest.mark.pagination
101100 self.create_many_objects(session, 5)
102101 res = test_client.get(self.page_url(2, 5))
103102 assert res.status_code == 200
104 assert res.json == {u'data': []}
103 assert res.json == {'data': []}
105104
106105 @pytest.mark.usefixtures('pagination_test_logic')
107106 @pytest.mark.pagination
0 # -*- coding: utf8 -*-
10 '''
21 Faraday Penetration Test IDE
32 Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/)
98 import pytest
109
1110 from tests.factories import SearchFilterFactory, UserFactory
12 from tests.test_api_non_workspaced_base import ReadWriteAPITests
11 from tests.test_api_non_workspaced_base import (
12 ReadWriteAPITests,
13 BulkUpdateTestsMixin,
14 BulkDeleteTestsMixin
15 )
1316 from tests.test_api_agent import logout
1417 from tests.conftest import login_as
1518 from faraday.server.models import SearchFilter
1821
1922
2023 @pytest.mark.usefixtures('logged_user')
21 class TestSearchFilterAPI(ReadWriteAPITests):
24 class TestSearchFilterAPI(ReadWriteAPITests, BulkUpdateTestsMixin, BulkDeleteTestsMixin):
2225 model = SearchFilter
2326 factory = SearchFilterFactory
2427 api_endpoint = 'searchfilter'
117120
118121 def test_patch_update_an_object_does_not_fail_with_partial_data(self, test_client, logged_user):
119122 self.first_object.creator = logged_user
120 super().test_update_an_object_fails_with_empty_dict(test_client, logged_user)
123 super().test_patch_update_an_object_does_not_fail_with_partial_data(test_client, logged_user)
124
125 @pytest.mark.usefixtures('ignore_nplusone')
126 def test_bulk_delete(self, test_client, logged_user):
127 for obj in self.model.query.all():
128 obj.creator = logged_user
129 super().test_bulk_delete(test_client)
0 # -*- coding: utf8 -*-
10 '''
21 Faraday Penetration Test IDE
32 Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/)
1514
1615 from faraday.server.api.modules.services import ServiceView
1716 from tests import factories
18 from tests.test_api_workspaced_base import ReadWriteAPITests
17 from tests.test_api_workspaced_base import ReadWriteAPITests, BulkDeleteTestsMixin, BulkUpdateTestsMixin
1918 from faraday.server.models import (
20 Service
19 Service, Credential, Vulnerability
2120 )
22 from tests.factories import HostFactory, EmptyCommandFactory
21 from tests.factories import HostFactory, EmptyCommandFactory, CredentialFactory, VulnerabilityFactory
2322
2423
2524 @pytest.mark.usefixtures('logged_user')
26 class TestListServiceView(ReadWriteAPITests):
25 class TestListServiceView(ReadWriteAPITests, BulkUpdateTestsMixin, BulkDeleteTestsMixin):
2726 model = Service
2827 factory = factories.ServiceFactory
2928 api_endpoint = 'services'
4342 assert res.status_code == 200
4443 assert 'services' in res.json
4544 for service in res.json['services']:
46 assert set([u'id', u'key', u'value']) == set(service.keys())
45 assert {'id', 'key', 'value'} == set(service.keys())
4746 object_properties = [
48 u'status',
49 u'protocol',
50 u'description',
51 u'_rev',
52 u'owned',
53 u'owner',
54 u'credentials',
55 u'name',
56 u'version',
57 u'_id',
58 u'metadata'
47 'status',
48 'protocol',
49 'description',
50 '_rev',
51 'owned',
52 'owner',
53 'credentials',
54 'name',
55 'version',
56 '_id',
57 'metadata'
5958 ]
6059 expected = set(object_properties)
6160 result = set(service['value'].keys())
334333 res = test_client.post(self.url(), data=data)
335334 print(res.data)
336335 assert res.status_code == 400
336
337 @pytest.mark.usefixtures('ignore_nplusone')
338 def test_bulk_update_cant_change_id(self, test_client):
339 super().test_bulk_update_cant_change_id(test_client)
340
341 def test_bulk_delete_with_references(self, test_client, session):
342 previous_creds = Credential.query.count()
343 previous_vulns = Vulnerability.query.count()
344 previous_services = Service.query.count()
345
346 service_1 = self.factory.create(workspace=self.workspace)
347 service_2 = self.factory.create(workspace=self.workspace)
348 service_3 = self.factory.create(workspace=self.workspace)
349
350 for _ in range(3):
351 CredentialFactory.create(service=service_1, workspace=self.workspace)
352 VulnerabilityFactory.create(service=service_2, workspace=self.workspace)
353 CredentialFactory.create(service=service_3, workspace=self.workspace)
354 VulnerabilityFactory.create(service=service_3, workspace=self.workspace)
355 session.commit()
356
357 raw_data = {'ids': [service_1.id, service_2.id, service_3.id]}
358 res = test_client.delete(self.url(), data=raw_data)
359
360 assert res.status_code == 200
361 assert res.json['deleted'] == 3
0 # -*- coding: utf8 -*-
10 '''
21 Faraday Penetration Test IDE
32 Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/)
43 See the file 'doc/LICENSE' for the license information
5
64 '''
75 import csv
86 import json
97 import urllib
108 import datetime
11 from builtins import str
129 from pathlib import Path
1310 from tempfile import NamedTemporaryFile
1411 from base64 import b64encode
1512 from io import BytesIO, StringIO
16 from posixpath import join as urljoin
13 from posixpath import join
14
15 from sqlalchemy.orm.util import was_deleted
1716
1817 try:
1918 from urllib import urlencode
2019 except ImportError:
21 from urllib.parse import urlencode
20 from urllib.parse import urlencode, urljoin
2221
2322 import pytz
2423 import pytest
3736 from tests import factories
3837 from tests.conftest import TEST_DATA_PATH
3938 from tests.test_api_workspaced_base import (
40 ReadWriteAPITests
39 ReadWriteAPITests,
40 BulkDeleteTestsMixin,
41 BulkUpdateTestsMixin
4142 )
4243 from faraday.server.models import (
4344 VulnerabilityGeneric,
5051 File,
5152 Host,
5253 Service,
53 CVE)
54 CVE,
55 CVSSV2,
56 CVSSV3,
57 SeveritiesHistogram,
58 )
5459 from tests.factories import (
5560 ServiceFactory,
5661 CommandFactory,
175180
176181
177182 @pytest.mark.usefixtures('logged_user')
178 class TestListVulnerabilityView(ReadWriteAPITests):
183 class TestListVulnerabilityView(ReadWriteAPITests, BulkUpdateTestsMixin, BulkDeleteTestsMixin):
179184 model = Vulnerability
180185 factory = factories.VulnerabilityFactory
181186 api_endpoint = 'vulns'
182187 # unique_fields = ['ip']
183188 # update_fields = ['ip', 'description', 'os']
184189 view_class = VulnerabilityView
185 patchable_fields = ['description']
190 patchable_fields = ['name']
186191
187192 def test_backward_json_compatibility(self, test_client, second_workspace, session):
188193 new_obj = self.factory.create(workspace=second_workspace)
192197 assert res.status_code == 200
193198 assert 'vulnerabilities' in res.json
194199 for vuln in res.json['vulnerabilities']:
195 assert set([u'id', u'key', u'value']) == set(vuln.keys())
200 assert {'id', 'key', 'value'} == set(vuln.keys())
196201 object_properties = [
197 u'status',
198 u'issuetracker',
199 u'description',
200 u'parent',
201 u'tags',
202 u'severity',
203 u'_rev',
204 u'easeofresolution',
205 u'owned',
206 u'hostnames',
207 u'pname',
208 u'query',
209 u'owner',
210 u'path',
211 u'data',
212 u'response',
213 u'refs',
214 u'desc',
215 u'impact',
216 u'confirmed',
217 u'name',
218 u'service',
219 u'obj_id',
220 u'type',
221 u'policyviolations',
222 u'request',
223 u'_attachments',
224 u'target',
225 u'_id',
226 u'resolution',
227 u'method',
228 u'metadata',
229 u'website',
230 u'params',
202 'status',
203 'issuetracker',
204 'description',
205 'parent',
206 'tags',
207 'severity',
208 '_rev',
209 'easeofresolution',
210 'owned',
211 'hostnames',
212 'pname',
213 'query',
214 'owner',
215 'path',
216 'data',
217 'response',
218 'refs',
219 'desc',
220 'impact',
221 'confirmed',
222 'name',
223 'service',
224 'obj_id',
225 'type',
226 'policyviolations',
227 'request',
228 '_attachments',
229 'target',
230 '_id',
231 'resolution',
232 'method',
233 'metadata',
234 'website',
235 'params',
231236 ]
232237 expected = set(object_properties)
233238 result = set(vuln['value'].keys())
294299 res = test_client.get(self.url(vuln))
295300 assert res.status_code == 200
296301 assert isinstance(res.json['hostnames'], list)
297 assert set(res.json['hostnames']) == set(hostname.name for hostname in
298 host_with_hostnames.hostnames)
302 assert set(res.json['hostnames']) == {hostname.name for hostname in
303 host_with_hostnames.hostnames}
299304
300305 def test_create_vuln(self, host_with_hostnames, test_client, session):
301306 """
329334 assert res.json['description'] == 'helloworld'
330335 assert res.json['severity'] == 'low'
331336
337 def test_histogram_creation(self, vulnerability_factory, second_workspace, test_client, session):
338 """
339 This one should only check basic vuln properties
340 :param host_with_hostnames:
341 :param test_client:
342 :param session:
343 :return:
344 """
345
346 vulns = VulnerabilityWeb.query.all()
347 for vuln in vulns:
348 session.delete(vuln)
349 session.commit()
350
351 vulns = Vulnerability.query.all()
352 for vuln in vulns:
353 session.delete(vuln)
354 session.commit()
355
356 session.query(SeveritiesHistogram).delete()
357 session.commit()
358 vulns_unconfirmed = vulnerability_factory.create_batch(4, confirmed=False,
359 workspace=self.workspace,
360 status='open',
361 severity='critical')
362
363 vulns_confirmed = vulnerability_factory.create_batch(4, confirmed=True,
364 workspace=self.workspace,
365 status='open',
366 severity='critical')
367
368 session.add_all(vulns_confirmed + vulns_unconfirmed)
369 session.commit()
370
371 histogram = SeveritiesHistogram.query.all()
372 assert len(histogram) == 1
373 assert histogram[0].workspace_id == self.workspace.id
374 assert histogram[0].critical == 8
375 assert histogram[0].confirmed == 4
376 assert histogram[0].date == datetime.date.today()
377
378 vulns_high = vulnerability_factory.create_batch(4,
379 confirmed=True,
380 workspace=second_workspace,
381 status='open',
382 severity='high')
383
384 owner = UserFactory.create()
385 service = ServiceFactory.create(workspace=self.workspace)
386 vuln_web = VulnerabilityWebFactory.create(
387 confirmed=True,
388 service=service,
389 creator=owner,
390 workspace=self.workspace,
391 severity='medium'
392 )
393
394 vulns_critical = vulnerability_factory.create_batch(4,
395 confirmed=False,
396 workspace=second_workspace,
397 status='open',
398 severity='critical')
399
400 session.add_all(vulns_high + vulns_critical + [vuln_web])
401 session.commit()
402
403 vhigh_id = vulns_high[0].id
404 vhigh2_id = vulns_high[1].id
405 vhigh3_id = vulns_high[2].id
406
407 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == self.workspace).all()
408 assert len(histogram) == 1
409 assert histogram[0].workspace_id == self.workspace.id
410 assert histogram[0].critical == 8
411 assert histogram[0].medium == 1
412 assert histogram[0].confirmed == 5
413
414 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == second_workspace).all()
415 assert len(histogram) == 1
416 assert histogram[0].workspace_id == second_workspace.id
417 assert histogram[0].critical == 4
418 assert histogram[0].high == 4
419 assert histogram[0].medium == 0
420 assert histogram[0].confirmed == 4
421 assert histogram[0].date == datetime.date.today()
422
423 v = Vulnerability.query.get(vhigh_id)
424 v.confirmed = False
425 session.commit()
426
427 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == second_workspace).all()
428 assert len(histogram) == 1
429 assert histogram[0].workspace_id == second_workspace.id
430 assert histogram[0].critical == 4
431 assert histogram[0].high == 4
432 assert histogram[0].confirmed == 3
433 assert histogram[0].date == datetime.date.today()
434
435 v = Vulnerability.query.get(vhigh_id)
436 v.status = 'closed'
437 session.commit()
438
439 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == second_workspace).all()
440 assert len(histogram) == 1
441 assert histogram[0].workspace_id == second_workspace.id
442 assert histogram[0].critical == 4
443 assert histogram[0].high == 3
444 assert histogram[0].confirmed == 3
445 assert histogram[0].date == datetime.date.today()
446
447 v = Vulnerability.query.get(vhigh_id)
448 v.status = 'closed'
449 session.commit()
450
451 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == second_workspace).all()
452 assert len(histogram) == 1
453 assert histogram[0].workspace_id == second_workspace.id
454 assert histogram[0].critical == 4
455 assert histogram[0].high == 3
456 assert histogram[0].confirmed == 3
457 assert histogram[0].date == datetime.date.today()
458
459 v = Vulnerability.query.get(vhigh_id)
460 v.status = 'open'
461 v.confirmed = False
462 session.commit()
463
464 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == second_workspace).all()
465 assert len(histogram) == 1
466 assert histogram[0].workspace_id == second_workspace.id
467 assert histogram[0].critical == 4
468 assert histogram[0].high == 4
469 assert histogram[0].confirmed == 3
470 assert histogram[0].date == datetime.date.today()
471
472 v = Vulnerability.query.get(vhigh_id)
473 v.status = 're-opened'
474 v.confirmed = True
475 session.commit()
476
477 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == second_workspace).all()
478 assert len(histogram) == 1
479 assert histogram[0].workspace_id == second_workspace.id
480 assert histogram[0].critical == 4
481 assert histogram[0].high == 4
482 assert histogram[0].confirmed == 4
483 assert histogram[0].date == datetime.date.today()
484
485 v = Vulnerability.query.get(vhigh_id)
486 v.status = 'risk-accepted'
487 session.commit()
488
489 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == second_workspace).all()
490 assert len(histogram) == 1
491 assert histogram[0].workspace_id == second_workspace.id
492 assert histogram[0].critical == 4
493 assert histogram[0].high == 3
494 assert histogram[0].confirmed == 3
495 assert histogram[0].date == datetime.date.today()
496
497 v = VulnerabilityWeb.query.get(vuln_web.id)
498 v.status = 'closed'
499 session.commit()
500
501 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == self.workspace).all()
502 assert len(histogram) == 1
503 assert histogram[0].workspace_id == self.workspace.id
504 assert histogram[0].critical == 8
505 assert histogram[0].medium == 0
506 assert histogram[0].date == datetime.date.today()
507 assert histogram[0].confirmed == 4
508
509 v = VulnerabilityWeb.query.get(vuln_web.id)
510 v.status = 'closed'
511 session.commit()
512
513 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == self.workspace).all()
514 assert len(histogram) == 1
515 assert histogram[0].workspace_id == self.workspace.id
516 assert histogram[0].critical == 8
517 assert histogram[0].medium == 0
518 assert histogram[0].date == datetime.date.today()
519 assert histogram[0].confirmed == 4
520
521 v = VulnerabilityWeb.query.get(vuln_web.id)
522 v.status = 'open'
523 session.commit()
524
525 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == self.workspace).all()
526 assert len(histogram) == 1
527 assert histogram[0].workspace_id == self.workspace.id
528 assert histogram[0].critical == 8
529 assert histogram[0].medium == 1
530 assert histogram[0].date == datetime.date.today()
531 assert histogram[0].confirmed == 5
532
533 v = VulnerabilityWeb.query.get(vuln_web.id)
534 v.status = 're-opened'
535 session.commit()
536
537 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == self.workspace).all()
538 assert len(histogram) == 1
539 assert histogram[0].workspace_id == self.workspace.id
540 assert histogram[0].critical == 8
541 assert histogram[0].medium == 1
542 assert histogram[0].date == datetime.date.today()
543 assert histogram[0].confirmed == 5
544
545 v = VulnerabilityWeb.query.get(vuln_web.id)
546 v.status = 'risk-accepted'
547 session.commit()
548
549 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == self.workspace).all()
550 assert len(histogram) == 1
551 assert histogram[0].workspace_id == self.workspace.id
552 assert histogram[0].critical == 8
553 assert histogram[0].medium == 0
554 assert histogram[0].date == datetime.date.today()
555 assert histogram[0].confirmed == 4
556
557 v = VulnerabilityWeb.query.get(vuln_web.id)
558 v.status = 'closed'
559 session.commit()
560
561 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == self.workspace).all()
562 assert len(histogram) == 1
563 assert histogram[0].workspace_id == self.workspace.id
564 assert histogram[0].critical == 8
565 assert histogram[0].medium == 0
566 assert histogram[0].date == datetime.date.today()
567 assert histogram[0].confirmed == 4
568
569 v = Vulnerability.query.get(vhigh_id)
570 v.confirmed = False
571 session.commit()
572
573 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == second_workspace).all()
574 assert len(histogram) == 1
575 assert histogram[0].workspace_id == second_workspace.id
576 assert histogram[0].critical == 4
577 assert histogram[0].high == 3
578 assert histogram[0].confirmed == 3
579 assert histogram[0].date == datetime.date.today()
580
581 v = Vulnerability.query.get(vhigh_id)
582 v.confirmed = True
583 session.commit()
584
585 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == second_workspace).all()
586 assert len(histogram) == 1
587 assert histogram[0].workspace_id == second_workspace.id
588 assert histogram[0].critical == 4
589 assert histogram[0].high == 3
590 assert histogram[0].confirmed == 3
591 assert histogram[0].date == datetime.date.today()
592
593 v = Vulnerability.query.get(vhigh_id)
594 v.status = "re-opened"
595 v.confirmed = True
596 session.commit()
597
598 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == second_workspace).all()
599 assert len(histogram) == 1
600 assert histogram[0].workspace_id == second_workspace.id
601 assert histogram[0].critical == 4
602 assert histogram[0].high == 4
603 assert histogram[0].confirmed == 4
604 assert histogram[0].date == datetime.date.today()
605
606 v = Vulnerability.query.get(vhigh_id)
607 v.confirmed = False
608 session.commit()
609
610 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == second_workspace).all()
611 assert len(histogram) == 1
612 assert histogram[0].workspace_id == second_workspace.id
613 assert histogram[0].critical == 4
614 assert histogram[0].high == 4
615 assert histogram[0].confirmed == 3
616 assert histogram[0].date == datetime.date.today()
617
618 v = session.query(Vulnerability).filter(Vulnerability.id == vhigh_id).first()
619 session.delete(v)
620 session.commit()
621
622 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == second_workspace).all()
623 assert len(histogram) == 1
624 assert histogram[0].workspace_id == second_workspace.id
625 assert histogram[0].critical == 4
626 assert histogram[0].high == 3
627 assert histogram[0].confirmed == 3
628 assert histogram[0].date == datetime.date.today()
629
630 Vulnerability.query.filter(Vulnerability.id.in_([vhigh2_id, vhigh3_id])).delete(synchronize_session=False)
631 session.commit()
632
633 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == second_workspace).all()
634 assert len(histogram) == 1
635 assert histogram[0].workspace_id == second_workspace.id
636 assert histogram[0].critical == 4
637 assert histogram[0].high == 1
638 assert histogram[0].confirmed == 1
639 assert histogram[0].date == datetime.date.today()
640
641 v = VulnerabilityWeb.query.get(vuln_web.id)
642 v.status = 'open'
643 session.commit()
644
645 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == self.workspace).all()
646 assert len(histogram) == 1
647 assert histogram[0].workspace_id == self.workspace.id
648 assert histogram[0].critical == 8
649 assert histogram[0].medium == 1
650 assert histogram[0].date == datetime.date.today()
651 assert histogram[0].confirmed == 5
652
653 VulnerabilityWeb.query.filter(VulnerabilityWeb.id == vuln_web.id).delete()
654 session.commit()
655
656 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == self.workspace).all()
657 assert len(histogram) == 1
658 assert histogram[0].workspace_id == self.workspace.id
659 assert histogram[0].critical == 8
660 assert histogram[0].medium == 0
661 assert histogram[0].confirmed == 4
662 assert histogram[0].date == datetime.date.today()
663
664 Vulnerability.query.filter(Vulnerability.workspace == self.workspace,
665 Vulnerability.status == 'open',
666 Vulnerability.severity == 'critical',
667 Vulnerability.confirmed == False).update({'status': 'closed'}) # noqa: E712
668 session.commit()
669
670 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == self.workspace).all()
671 assert len(histogram) == 1
672 assert histogram[0].workspace_id == self.workspace.id
673 assert histogram[0].critical == 4
674 assert histogram[0].medium == 0
675 assert histogram[0].date == datetime.date.today()
676 assert histogram[0].confirmed == 4
677
678 Vulnerability.query.filter(Vulnerability.workspace == self.workspace,
679 Vulnerability.status == 'open',
680 Vulnerability.severity == 'critical').update({'severity': 'medium'})
681 session.commit()
682
683 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == self.workspace).all()
684 assert len(histogram) == 1
685 assert histogram[0].workspace_id == self.workspace.id
686 assert histogram[0].critical == 0
687 assert histogram[0].medium == 4
688 assert histogram[0].date == datetime.date.today()
689 assert histogram[0].confirmed == 4
690
691 Vulnerability.query.filter(Vulnerability.workspace == self.workspace,
692 Vulnerability.status == 'open',
693 Vulnerability.severity == 'medium').update({'severity': 'critical', 'status': 'closed'})
694 session.commit()
695
696 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == self.workspace).all()
697 assert len(histogram) == 1
698 assert histogram[0].workspace_id == self.workspace.id
699 assert histogram[0].critical == 0
700 assert histogram[0].medium == 0
701 assert histogram[0].date == datetime.date.today()
702 assert histogram[0].confirmed == 0
703
704 Vulnerability.query.filter(Vulnerability.workspace == self.workspace,
705 Vulnerability.status == 'closed',
706 Vulnerability.severity == 'critical',
707 Vulnerability.confirmed == True).update({'severity': 'medium', 'status': 're-opened'}) # noqa: E712
708 session.commit()
709
710 histogram = SeveritiesHistogram.query.filter(SeveritiesHistogram.workspace == self.workspace).all()
711 assert len(histogram) == 1
712 assert histogram[0].workspace_id == self.workspace.id
713 assert histogram[0].critical == 0
714 assert histogram[0].medium == 4
715 assert histogram[0].date == datetime.date.today()
716 assert histogram[0].confirmed == 4
717
332718 def test_create_cannot_create_vuln_with_empty_name_fails(
333719 self, host, session, test_client):
334720 # I'm using this to test the NonBlankColumn which works for
409795 assert filename in res.json['_attachments']
410796 attachment.close()
411797 # check the attachment can be downloaded
412 res = test_client.get(urljoin(self.url(), f'{vuln_id}/attachment/{filename}'))
798 res = test_client.get(join(self.url(), f'{vuln_id}/attachment/{filename}'))
413799 assert res.status_code == 200
414800 assert res.data == file_content
415801
416 res = test_client.get(urljoin(
802 res = test_client.get(join(
417803 self.url(),
418804 f'{vuln_id}/attachment/notexistingattachment.png'
419805 ))
440826 res = test_client.put(self.url(obj=vuln, workspace=self.workspace), data=raw_data)
441827 assert res.status_code == 200
442828 filename = attachment.name.split('/')[-1]
443 res = test_client.get(urljoin(
829 res = test_client.get(join(
444830 self.url(), f'{vuln.id}/attachment/{filename}'
445831 ))
446832 assert res.status_code == 200
464850 assert res.status_code == 200
465851
466852 # verify that the old file was deleted and the new one exists
467 res = test_client.get(urljoin(
853 res = test_client.get(join(
468854 self.url(), f'{vuln.id}/attachment/{filename}'
469855 ))
470856 assert res.status_code == 404
471 res = test_client.get(urljoin(
857 res = test_client.get(join(
472858 self.url(), f'{vuln.id}/attachment/{new_filename}'
473859 ))
474860 assert res.status_code == 200
488874 session.add(new_attach)
489875 session.commit()
490876
491 res = test_client.get(urljoin(self.url(workspace=workspace), f'{vuln.id}/attachment'))
877 res = test_client.get(join(self.url(workspace=workspace), f'{vuln.id}/attachment'))
492878 assert res.status_code == 200
493879 assert new_attach.filename in res.json
494880 assert 'image/png' in res.json[new_attach.filename]['content_type']
582968
583969 def _create_put_data(self,
584970 name, desc, status, parent, parent_type,
585 attachments=None, impact=None, refs=None,
586 policy_violations=None):
587 if not refs:
588 refs = []
589 if not policy_violations:
590 policy_violations = []
971 attachments=None, impact=None, refs=[],
972 policy_violations=[], cve=[]):
591973
592974 if not impact:
593975 impact = {"accountability": False, "availability": False, "confidentiality": False, "integrity": False}
6241006 "description": "",
6251007 "parent_type": parent_type,
6261008 "protocol": "",
627 "version": ""}
1009 "version": "",
1010 "cve": cve
1011 }
6281012
6291013 if attachments:
6301014 raw_data['_attachments'] = {}
6751059 res = test_client.put(self.url(vuln), data=raw_data)
6761060 assert res.status_code in [400, 409]
6771061 assert vuln_count_previous == session.query(Vulnerability).count()
1062
1063 def test_update_vuln_cve(self, test_client, session, host_with_hostnames):
1064 vuln = self.factory.create(status='open', cve=['CVE-2021-1234'], host=host_with_hostnames, service=None,
1065 workspace=host_with_hostnames.workspace)
1066 session.add(vuln)
1067 session.commit()
1068
1069 vuln = self.factory.create(status='open', cve=['CVE-2021-1234'], host=host_with_hostnames, service=None,
1070 workspace=host_with_hostnames.workspace)
1071 session.add(vuln)
1072 session.commit()
1073
1074 raw_data = self._create_put_data(
1075 name='New name',
1076 desc='New desc',
1077 status='open',
1078 parent=vuln.host.id,
1079 parent_type='Host',
1080 policy_violations=['pv0'],
1081 cve=['cve-2021-1234']
1082 )
1083 vuln_count_previous = session.query(CVE).count()
1084 assert vuln_count_previous == 1
1085 res = test_client.put(self.url(vuln), data=raw_data)
1086 assert res.status_code == 200
1087 assert vuln_count_previous == session.query(CVE).count()
6781088
6791089 def test_create_vuln_web(self, host_with_hostnames, test_client, session):
6801090 service = ServiceFactory.create(host=host_with_hostnames, workspace=host_with_hostnames.workspace)
7241134 session.commit()
7251135 expected_ids = {vuln.id for vuln in expected_vulns}
7261136
727 res = test_client.get(
728 self.url(workspace=second_workspace) + f'?{param_name}=bbb')
1137 res = test_client.get(urljoin(
1138 self.url(workspace=second_workspace), f'?{param_name}=bbb'))
7291139 assert res.status_code == 200
7301140
7311141 for vuln in res.json['data']:
7321142 assert vuln['query'] == 'bbb'
733 assert set(vuln['_id'] for vuln in res.json['data']) == expected_ids
1143 assert {vuln['_id'] for vuln in res.json['data']} == expected_ids
7341144
7351145 @pytest.mark.usefixtures('mock_envelope_list')
7361146 @pytest.mark.parametrize('medium_name', ['medium', 'med'])
7571167 expected_ids.update(vuln.id for vuln in medium_vulns)
7581168 expected_ids.update(vuln.id for vuln in medium_vulns_web)
7591169
760 res = test_client.get(self.url(
761 workspace=second_workspace) + f'?severity={medium_name}')
1170 res = test_client.get(urljoin(self.url(
1171 workspace=second_workspace), f'?severity={medium_name}'))
7621172 assert res.status_code == 200
7631173 for vuln in res.json['data']:
7641174 assert vuln['severity'] == 'med'
765 assert set(vuln['_id'] for vuln in res.json['data']) == expected_ids
1175 assert {vuln['_id'] for vuln in res.json['data']} == expected_ids
7661176
7671177 def test_filter_by_invalid_severity_fails(self, test_client):
768 res = test_client.get(self.url() + '?severity=131231')
1178 res = test_client.get(urljoin(self.url(), '?severity=131231'))
7691179 assert res.status_code == 400
7701180 assert b'Invalid severity type' in res.data
7711181
7721182 @pytest.mark.usefixtures('mock_envelope_list')
7731183 def test_filter_by_invalid_severity(self, test_client):
774 res = test_client.get(self.url() + '?severity=invalid')
1184 res = test_client.get(urljoin(self.url(), '?severity=invalid'))
7751185 assert res.status_code == 400
7761186
7771187 @pytest.mark.usefixtures('mock_envelope_list')
7951205 expected_ids = {vuln.id for vuln in expected_vulns}
7961206
7971207 # This shouldn't show any vulns with POSTT method
798 res = test_client.get(self.url(
799 workspace=second_workspace) + '?method=POST')
800 assert res.status_code == 200
801 assert set(vuln['_id'] for vuln in res.json['data']) == expected_ids, "This may fail because no presence of " \
1208 res = test_client.get(urljoin(self.url(
1209 workspace=second_workspace), '?method=POST'))
1210 assert res.status_code == 200
1211 assert {vuln['_id'] for vuln in res.json['data']} == expected_ids, "This may fail because no presence of " \
8021212 "filter_alchemy branch"
8031213
8041214 # This shouldn't show any vulns since by default method filter is
8051215 # an exact match, not a like statement
806 res = test_client.get(self.url(
807 workspace=second_workspace) + '?method=%25POST%25')
1216 res = test_client.get(urljoin(self.url(
1217 workspace=second_workspace), '?method=%25POST%25'))
8081218 assert res.status_code == 200
8091219 assert len(res.json['data']) == 0
8101220
8291239 session.commit()
8301240 expected_ids = {vuln.id for vuln in expected_vulns}
8311241
832 res = test_client.get(self.url(
833 workspace=second_workspace) + '?website=faradaysec.com')
1242 res = test_client.get(urljoin(self.url(
1243 workspace=second_workspace), '?website=faradaysec.com'))
8341244 assert res.status_code == 200
8351245
8361246 for vuln in res.json['data']:
8371247 assert vuln['website'] == 'faradaysec.com'
838 assert set(vuln['_id'] for vuln in res.json['data']) == expected_ids
1248 assert {vuln['_id'] for vuln in res.json['data']} == expected_ids
8391249
8401250 @pytest.mark.usefixtures('mock_envelope_list')
8411251 def test_filter_by_target(self, test_client, session, host_factory,
8611271 expected_ids.add(service_vuln.id)
8621272 expected_ids.add(web_vuln.id)
8631273
864 res = test_client.get(self.url() + '?target=9.9.9.9')
1274 res = test_client.get(urljoin(self.url(), '?target=9.9.9.9'))
8651275 assert res.status_code == 200
8661276 for vuln in res.json['data']:
8671277 assert vuln['target'] == '9.9.9.9'
868 assert set(vuln['_id'] for vuln in res.json['data']) == expected_ids
1278 assert {vuln['_id'] for vuln in res.json['data']} == expected_ids
8691279
8701280 @pytest.mark.usefixtures('ignore_nplusone')
8711281 @pytest.mark.parametrize('filter_params', [
10401450 f'"op":"{operation["filter_operation"]}",' \
10411451 f'"val": {operation["filter_value"]} }}]}}'
10421452
1043 res = test_client.get(urljoin(self.url(), qparams))
1453 res = test_client.get(join(self.url(), qparams))
10441454
10451455 assert res.status_code == operation['res_status_code']
10461456 assert len(res.json['vulnerabilities']) == operation['count']
10571467 f'{{"name": "{filter_params["filter_field_name"]}", '\
10581468 f'"op":"{operation["filter_operation"]}",'\
10591469 f'"val": {operation["filter_value"]} }}], {orderparams} }}'
1060 res = test_client.get(urljoin(self.url(), qparams))
1470 res = test_client.get(join(self.url(), qparams))
10611471
10621472 assert res.status_code == operation['res_status_code']
10631473 assert len(res.json['vulnerabilities']) == operation['count']
10741484 f'{{"name": "{filter_params["filter_field_name"]}", '\
10751485 f'"op":"{operation["filter_operation"]}",'\
10761486 f'"val": {operation["filter_value"]} }}], {groupparams} }}'
1077 res = test_client.get(urljoin(self.url(), qparams))
1487 res = test_client.get(join(self.url(), qparams))
10781488
10791489 assert res.status_code == 200
10801490
12271637 res = test_client.post(self.url(workspace=ws), data=raw_data)
12281638 assert res.status_code == 201
12291639 assert len(res.json['cve']) == 3
1230 assert set(res.json['cve']) == set(['CVE-2018-1234', 'CVE-2017-0002', 'CVE-2017-0012'])
1640 assert set(res.json['cve']) == {'CVE-2018-1234', 'CVE-2017-0002', 'CVE-2017-0012'}
1641
1642 def test_create_vuln_with_cvssv2(self, host_with_hostnames, test_client, session):
1643 cvssv2_obj = CVSSV2(vector_string='AV:L/AC:L/Au:M/C:C/I:P/A:N')
1644 session.add(cvssv2_obj)
1645 session.commit()
1646
1647 assert session.query(CVSSV2).count() == 1
1648 assert cvssv2_obj.base_score == 5.0
1649
1650 cvssv2_obj = CVSSV2(vector_string='AV:N/AC:L/Au:M/C:C/I:P/A:N')
1651
1652 session.add(cvssv2_obj)
1653 session.commit()
1654
1655 assert session.query(CVSSV2).count() == 2
1656 assert cvssv2_obj.base_score == 6.8
1657
1658 cvssv2_obj = CVSSV2(base_score=4.5)
1659 session.add(cvssv2_obj)
1660 session.commit()
1661
1662 assert session.query(CVSSV2).count() == 3
1663 assert cvssv2_obj.base_score == 4.5
1664
1665 cvssv2_obj = CVSSV2()
1666 session.add(cvssv2_obj)
1667 session.commit()
1668
1669 assert session.query(CVSSV2).count() == 4
1670 assert cvssv2_obj.base_score is None
1671
1672 cvssv2_obj = CVSSV2(vector_string='AV:N/AC:H/Au:N/C:P/I:N/A:N')
1673 assert cvssv2_obj.base_score == 2.6
1674
1675 cvssv2_obj = CVSSV2(vector_string='AV:N/AC:L/Au:M/C:N/I:P/A:P')
1676 assert cvssv2_obj.base_score == 4.7
1677
1678 cvssv2_obj = CVSSV2(vector_string='AV:N/AC:H/Au:M/C:N/I:P/A:P')
1679 assert cvssv2_obj.base_score == 3.2
1680
1681 cvssv2_obj = CVSSV2(vector_string='AV:N/AC:L/Au:M/C:N/I:P/A:P')
1682 assert cvssv2_obj.base_score == 4.7
1683
1684 cvssv2_obj = CVSSV2(vector_string='AV:N/AC:L/Au:N/C:N/I:P/A:P')
1685 assert cvssv2_obj.base_score == 6.4
1686
1687 cvssv2_obj = CVSSV2(vector_string='AV:A/AC:L/Au:N/C:N/I:P/A:P')
1688 assert cvssv2_obj.base_score == 4.8
1689
1690 cvssv2_obj = CVSSV2(vector_string='AV:N/AC:L/Au:N/C:N/I:P/A:P')
1691 assert cvssv2_obj.base_score == 6.4
1692
1693 cvssv2_obj = CVSSV2(vector_string='AV:N/AC:H/Au:N/C:P/I:P/A:P')
1694 assert cvssv2_obj.base_score == 5.1
1695
1696 cvssv2_obj = CVSSV2(vector_string='AV:N/AC:H/Au:N/C:P/I:N/A:P')
1697 assert cvssv2_obj.base_score == 4.0
1698
1699 cvssv2_obj = CVSSV2(vector_string='AV:N/AC:H/Au:N/C:C/I:C/A:C')
1700 assert cvssv2_obj.base_score == 7.6
1701
1702 cvssv2_obj = CVSSV2(vector_string='AV:L/AC:L/Au:N/C:C/I:C/A:N')
1703 assert cvssv2_obj.base_score == 6.6
1704
1705 cvssv2_obj = CVSSV2(vector_string='AV:L/AC:L/Au:N/C:C/I:C/A:P')
1706 assert cvssv2_obj.base_score == 6.8
1707
1708 cvssv2_obj = CVSSV2(vector_string='AV:L/AC:L/Au:N/C:C/I:C/A:C')
1709 assert cvssv2_obj.base_score == 7.2
1710
1711 cvssv2_obj = CVSSV2(vector_string='AV:L/AC:L/Au:N/C:C/I:N/A:C')
1712 assert cvssv2_obj.base_score == 6.6
1713
1714 cvssv2_obj = CVSSV2(vector_string='AV:L/AC:L/Au:N/C:N/I:P/A:P')
1715 assert cvssv2_obj.base_score == 3.6
1716
1717 def test_create_vuln_with_cvssv3(self, host_with_hostnames, test_client, session):
1718 cvssv3_obj = CVSSV3(vector_string='AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L')
1719 session.add(cvssv3_obj)
1720 session.commit()
1721
1722 assert session.query(CVSSV3).count() == 1
1723 assert cvssv3_obj.base_score == 9.9
1724
1725 cvssv3_obj = CVSSV3(vector_string='AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:L')
1726 session.add(cvssv3_obj)
1727 session.commit()
1728
1729 assert session.query(CVSSV3).count() == 2
1730 assert cvssv3_obj.base_score == 7.3
1731
1732 cvssv3_obj = CVSSV3(base_score=4.5)
1733 session.add(cvssv3_obj)
1734 session.commit()
1735
1736 assert session.query(CVSSV3).count() == 3
1737 assert cvssv3_obj.base_score == 4.5
1738
1739 cvssv3_obj = CVSSV3()
1740 session.add(cvssv3_obj)
1741 session.commit()
1742
1743 assert session.query(CVSSV3).count() == 4
1744 assert cvssv3_obj.base_score is None
1745
1746 cvssv3_obj = CVSSV3(vector_string='AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:N')
1747 assert cvssv3_obj.base_score == 0.0
1748
1749 cvssv3_obj = CVSSV3(vector_string='AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N')
1750 assert cvssv3_obj.base_score == 4.3
1751
1752 cvssv3_obj = CVSSV3(vector_string='AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N')
1753 assert cvssv3_obj.base_score == 7.1
1754
1755 cvssv3_obj = CVSSV3(vector_string='AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N')
1756 assert cvssv3_obj.base_score == 8.1
1757
1758 cvssv3_obj = CVSSV3(vector_string='AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L')
1759 assert cvssv3_obj.base_score == 8.3
1760
1761 cvssv3_obj = CVSSV3(vector_string='AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H')
1762 assert cvssv3_obj.base_score == 8.8
1763
1764 cvssv3_obj = CVSSV3(vector_string='AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:N')
1765 assert cvssv3_obj.base_score == 0.0
1766
1767 cvssv3_obj = CVSSV3(vector_string='AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N')
1768 assert cvssv3_obj.base_score == 5.0
1769
1770 cvssv3_obj = CVSSV3(vector_string='AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N')
1771 assert cvssv3_obj.base_score == 7.7
1772
1773 cvssv3_obj = CVSSV3(vector_string='AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N')
1774 assert cvssv3_obj.base_score == 8.5
1775
1776 cvssv3_obj = CVSSV3(vector_string='AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N')
1777 assert cvssv3_obj.base_score == 9.6
1778
1779 cvssv3_obj = CVSSV3(vector_string='AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L')
1780 assert cvssv3_obj.base_score == 9.9
1781
1782 cvssv3_obj = CVSSV3(vector_string='AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H')
1783 assert cvssv3_obj.base_score == 9.9
1784
1785 cvssv3_obj = CVSSV3(vector_string='AV:P/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H')
1786 assert cvssv3_obj.base_score == 7.4
1787
1788 cvssv3_obj = CVSSV3(vector_string='AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H')
1789 assert cvssv3_obj.base_score == 8.8
1790
1791 cvssv3_obj = CVSSV3(vector_string='AV:A/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H')
1792 assert cvssv3_obj.base_score == 9.0
1793
1794 cvssv3_obj = CVSSV3(vector_string='AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H')
1795 assert cvssv3_obj.base_score == 8.2
12311796
12321797 def test_create_vuln_with_policyviolations(self, host_with_hostnames, test_client, session):
12331798 session.commit() # flush host_with_hostnames
12691834 assert res.status_code == 201
12701835 assert vuln_count_previous + 1 == session.query(Vulnerability).count()
12711836 assert res.json['name'] == 'New vulns'
1272 assert res.json['impact'] == {u'accountability': True,
1273 u'availability': True,
1274 u'confidentiality': True,
1275 u'integrity': True}
1837 assert res.json['impact'] == {'accountability': True,
1838 'availability': True,
1839 'confidentiality': True,
1840 'integrity': True}
12761841
12771842 def test_handles_invalid_impact(self, host_with_hostnames, test_client,
12781843 session):
14221987
14231988 # Desc
14241989 res = test_client.get(
1425 urljoin(self.url(), "count?confirmed=1&group_by=severity&order=sc"
1990 join(self.url(), "count?confirmed=1&group_by=severity&order=sc"
14261991 ))
14271992 assert res.status_code == 400
14281993
14291994 # Asc
1430 res = test_client.get(urljoin(self.url(), "count?confirmed=1&group_by=severity&order=name,asc"))
1995 res = test_client.get(join(self.url(), "count?confirmed=1&group_by=severity&order=name,asc"))
14311996 assert res.status_code == 400
14321997
14331998 def test_count_order_by(self, test_client, session):
14442009
14452010 # Desc
14462011 res = test_client.get(
1447 urljoin(self.url(), "count?confirmed=1&group_by=severity&order=desc"
2012 join(self.url(), "count?confirmed=1&group_by=severity&order=desc"
14482013 ))
14492014 assert res.status_code == 200
14502015 assert res.json['total_count'] == 3
14552020
14562021 # Asc
14572022 res = test_client.get(
1458 urljoin(self.url(), "count?confirmed=1&group_by=severity&order=asc"))
2023 join(self.url(), "count?confirmed=1&group_by=severity&order=asc"))
14592024 assert res.status_code == 200
14602025 assert res.json['total_count'] == 3
14612026 assert sorted(res.json['groups'], key=lambda i: (i['name'], i['count'], i['severity']), reverse=True) == sorted(
14762041 session.add(vuln)
14772042 session.commit()
14782043
1479 res = test_client.get(urljoin(self.url(), "count?confirmed=1&group_by=username"))
2044 res = test_client.get(join(self.url(), "count?confirmed=1&group_by=username"))
14802045 assert res.status_code == 400
14812046
1482 res = test_client.get(urljoin(self.url(), "count?confirmed=1&group_by="))
2047 res = test_client.get(join(self.url(), "count?confirmed=1&group_by="))
14832048 assert res.status_code == 400
14842049
14852050 def test_count_confirmed(self, test_client, session):
14952060 session.add(vuln)
14962061 session.commit()
14972062
1498 res = test_client.get(urljoin(self.url(), 'count?confirmed=1&group_by=severity'))
2063 res = test_client.get(join(self.url(), 'count?confirmed=1&group_by=severity'))
14992064 assert res.status_code == 200
15002065 assert res.json['total_count'] == 3
15012066 assert sorted(res.json['groups'], key=lambda i: (i['count'], i['name'], i['severity'])) == sorted([
15142079 session.commit()
15152080
15162081 res = test_client.get(
1517 urljoin(self.url(workspace=second_workspace), 'count?group_by=severity'
2082 join(self.url(workspace=second_workspace), 'count?group_by=severity'
15182083 ))
15192084 assert res.status_code == 200
15202085 assert res.json['total_count'] == 9
15372102 session.commit()
15382103
15392104 res = test_client.get(
1540 urljoin(
2105 join(
15412106 self.url(),
15422107 f'count_multi_workspace?workspaces={self.workspace.name}&confirmed=1&group_by=severity&order=desc'
15432108 )
15692134 session.commit()
15702135
15712136 res = test_client.get(
1572 urljoin(
2137 join(
15732138 self.url(),
15742139 f'count_multi_workspace?workspaces={self.workspace.name},'
15752140 f'{second_workspace.name}&confirmed=1&group_by=severity&order=desc'
15822147
15832148 def test_count_multiworkspace_no_workspace_param(self, test_client):
15842149 res = test_client.get(
1585 urljoin(self.url(), 'count_multi_workspace?confirmed=1&group_by=severity&order=desc'
2150 join(self.url(), 'count_multi_workspace?confirmed=1&group_by=severity&order=desc'
15862151 ))
15872152 assert res.status_code == 400
15882153
15892154 def test_count_multiworkspace_no_groupby_param(self, test_client):
15902155 res = test_client.get(
1591 urljoin(self.url(), f'count_multi_workspace?workspaces={self.workspace.name}&confirmed=1&order=desc'
2156 join(self.url(), f'count_multi_workspace?workspaces={self.workspace.name}&confirmed=1&order=desc'
15922157 ))
15932158 assert res.status_code == 400
15942159
15952160 def test_count_multiworkspace_nonexistent_ws(self, test_client):
15962161 res = test_client.get(
1597 urljoin(
2162 join(
15982163 self.url(),
15992164 f'count_multi_workspace?workspaces=asdf,{self.workspace.name}&confirmed=1&group_by=severity&order=desc'
16002165 )
17002265 expected_ids.update(vuln.id for vuln in high_vulns)
17012266 web_expected_ids.update(vuln.id for vuln in high_vulns_web)
17022267
1703 res = test_client.get(self.url(
1704 workspace=second_workspace) + f'?command_id={command.id}')
2268 res = test_client.get(urljoin(self.url(
2269 workspace=second_workspace), f'?command_id={command.id}'))
17052270 assert res.status_code == 200
17062271 for vuln in res.json['data']:
17072272 command_object = CommandObject.query.filter_by(
17102275 workspace=second_workspace,
17112276 ).first()
17122277 vuln['metadata']['command_id'] == command_object.command.id
1713 assert set(vuln['_id'] for vuln in res.json['data']) == expected_ids
2278 assert {vuln['_id'] for vuln in res.json['data']} == expected_ids
17142279
17152280 # Check for web vulns
1716 res = test_client.get(self.url(
1717 workspace=second_workspace) + f'?command_id={web_command.id}')
2281 res = test_client.get(urljoin(self.url(
2282 workspace=second_workspace), f'?command_id={web_command.id}'))
17182283 assert res.status_code == 200
17192284 for vuln in res.json['data']:
17202285 command_object = CommandObject.query.filter_by(
17232288 workspace=second_workspace,
17242289 ).first()
17252290 vuln['metadata']['command_id'] == command_object.command.id
1726 assert set(vuln['_id'] for vuln in res.json['data']) == web_expected_ids
2291 assert {vuln['_id'] for vuln in res.json['data']} == web_expected_ids
17272292
17282293 # Check for cross-workspace bugs
1729 res = test_client.get(self.url(
1730 workspace=workspace) + f'?command_id={web_command.id}')
2294 res = test_client.get(urljoin(self.url(
2295 workspace=workspace), f'?command_id={web_command.id}'))
17312296 assert res.status_code == 200
17322297 assert len(res.json['data']) == 0
17332298
17642329 res.json['vulnerabilities']))
17652330 assert 'metadata' in from_json_vuln[0]['value']
17662331 expected_metadata = {
1767 u'command_id': command.id,
1768 u'create_time': pytz.UTC.localize(vuln.create_date).isoformat(),
1769 u'creator': command.tool,
1770 u'owner': owner.username,
1771 u'update_action': 0,
1772 u'update_controller_action': u'',
1773 u'update_time': pytz.UTC.localize(vuln.update_date).isoformat(),
1774 u'update_user': None
2332 'command_id': command.id,
2333 'create_time': pytz.UTC.localize(vuln.create_date).isoformat(),
2334 'creator': command.tool,
2335 'owner': owner.username,
2336 'update_action': 0,
2337 'update_controller_action': '',
2338 'update_time': pytz.UTC.localize(vuln.update_date).isoformat(),
2339 'update_user': None
17752340 }
17762341 assert expected_metadata == from_json_vuln[0]['value']['metadata']
17772342
19382503 )
19392504 ws_name = host_with_hostnames.workspace.name
19402505 res = test_client.post(
1941 urljoin(self.url(workspace=host_with_hostnames.workspace) + f'?command_id={command.id}'),
2506 urljoin(self.url(workspace=host_with_hostnames.workspace), f'?command_id={command.id}'),
19422507 data=raw_data
19432508 )
19442509 assert res.status_code == 201
19532518 severity='high',
19542519 )
19552520 res = test_client.put(
1956 urljoin(
2521 join(
19572522 self.url(workspace=host_with_hostnames.workspace), f'{res.json["_id"]}?command_id={command.id}'
19582523 ),
19592524 data=raw_data
19652530 service = ServiceFactory.create(workspace=self.workspace)
19662531 session.commit()
19672532 assert len(command.command_objects) == 0
1968 url = self.url(workspace=command.workspace) + '?' + urlencode({'command_id': command.id})
2533 url = urljoin(self.url(workspace=command.workspace), f"?{urlencode({'command_id': command.id})}")
19692534 raw_data = _create_post_data_vulnerability(
19702535 name='Update vulnsweb',
19712536 vuln_type='VulnerabilityWeb',
20552620
20562621 res = test_client.post(self.url(), data=raw_data)
20572622 assert res.status_code == 400
2058 assert res.json == {u'messages': {'json': {u'_schema': [u'Parent id not found: 358302']}}}
2623 assert res.json == {'messages': {'json': {'_schema': ['Parent id not found: 358302']}}}
20592624
20602625 def test_after_deleting_vuln_ref_and_policies_remains(self, session, test_client):
20612626 vuln = VulnerabilityFactory.create(workspace=self.workspace)
21012666 session.add(service)
21022667 session.add(hostname)
21032668 session.commit()
2104 url = self.url(workspace=workspace) + f'?hostnames={hostname.name}'
2669 url = urljoin(self.url(workspace=workspace), f'?hostnames={hostname.name}')
21052670 res = test_client.get(url)
21062671
21072672 assert res.status_code == 200
21202685 session.add(host)
21212686 session.add(hostname)
21222687 session.commit()
2123 url = self.url(workspace=workspace) + f'?hostnames={hostname.name}'
2688 url = urljoin(self.url(workspace=workspace), f'?hostnames={hostname.name}')
21242689 res = test_client.get(url)
21252690 assert res.status_code == 200
21262691 assert res.json['count'] == 1
21442709 session.commit()
21452710
21462711 # Search with hosnames=HA,HB
2147 res = test_client.get(self.url(workspace=vuln.workspace) + f'?hostname={hostnameA},{hostnameB}')
2712 res = test_client.get(urljoin(self.url(workspace=vuln.workspace), f'?hostname={hostnameA},{hostnameB}'))
21482713 assert res.status_code == 200
21492714 assert res.json['count'] == 2
21502715
26463211 "target", "desc", "status", "hostnames", "comments", "owner",
26473212 "os", "resolution", "refs", "easeofresolution", "web_vulnerability",
26483213 "data", "website", "path", "status_code", "request", "response", "method",
2649 "params", "pname", "query", "policyviolations", "external_id", "impact_confidentiality",
3214 "params", "pname", "query", "cve", "policyviolations", "external_id", "impact_confidentiality",
26503215 "impact_integrity", "impact_availability", "impact_accountability", "update_date",
26513216 "host_id", "host_description", "mac",
26523217 "host_owned", "host_creator_id", "host_date", "host_update_date",
26633228 session.add(confirmed_vulns)
26643229 session.commit()
26653230 res = test_client.get(
2666 urljoin(
3231 join(
26673232 self.url(workspace=workspace),
26683233 'export_csv?q={"filters":[{"name":"confirmed","op":"==","val":"true"}]}'
26693234 )
26743239 @pytest.mark.usefixtures('ignore_nplusone')
26753240 def test_export_vuln_csv_unicode_bug(self, test_client, session):
26763241 workspace = WorkspaceFactory.create()
2677 desc = u'Latin-1 Supplement \xa1 \xa2 \xa3 \xa4 \xa5 \xa6 \xa7 \xa8'
3242 desc = 'Latin-1 Supplement \xa1 \xa2 \xa3 \xa4 \xa5 \xa6 \xa7 \xa8'
26783243 confirmed_vulns = VulnerabilityFactory.create(
26793244 confirmed=True,
26803245 description=desc,
26813246 workspace=workspace)
26823247 session.add(confirmed_vulns)
26833248 session.commit()
2684 res = test_client.get(urljoin(self.url(workspace=workspace), 'export_csv'))
3249 res = test_client.get(join(self.url(workspace=workspace), 'export_csv'))
26853250 assert res.status_code == 200
26863251 assert self._verify_csv(res.data, confirmed=True)
26873252
26923257 session.add(confirmed_vulns)
26933258 session.commit()
26943259 res = test_client.get(
2695 urljoin(
3260 join(
26963261 self.url(workspace=workspace),
26973262 'export_csv?q={"filters":[{"name":"severity","op":"==","val":"critical"}]}'
26983263 )
27063271 session.add(self.first_object)
27073272 session.commit()
27083273 res = test_client.get(
2709 urljoin(self.url(), 'export_csv?confirmed=true')
3274 join(self.url(), 'export_csv?confirmed=true')
27103275 )
27113276 assert res.status_code == 200
27123277 self._verify_csv(res.data, confirmed=True)
27713336 session.add(vuln)
27723337 session.commit()
27733338
2774 res = test_client.get(urljoin(self.url(), 'export_csv'))
3339 res = test_client.get(join(self.url(), 'export_csv'))
27753340 assert self._verify_csv(res.data)
27763341
27773342 def _verify_csv(self, raw_csv_data, confirmed=False, severity=None):
28643429 assert new_attach.filename in res.json
28653430 assert 'image/png' in res.json[new_attach.filename]['content_type']
28663431
3432 @pytest.mark.usefixtures('ignore_nplusone')
3433 def test_bulk_update_vuln_cant_change_tool_type_or_attachments(self, test_client, session):
3434 host = HostFactory.create(workspace=self.workspace)
3435 tool = "tool_name"
3436 updated_tool = "new_tool"
3437 vuln = VulnerabilityFactory.create(workspace=self.workspace, host_id=host.id, tool=tool)
3438 session.add(vuln)
3439 session.commit() # flush host_with_hostnames
3440 type = "Vulnerability" if "web" in vuln.type.lower() else "Vulnerability"
3441 # flush host_with_hostnames
3442 attachment = NamedTemporaryFile()
3443 file_content = b'test file'
3444 attachment.write(file_content)
3445 attachment.seek(0)
3446 attachment_data = self._create_put_data(
3447 'Updated with attachment',
3448 'Updated vuln',
3449 'open',
3450 host.id,
3451 'Host',
3452 attachments=[attachment]
3453 )["_attachments"]
3454 raw_data = {'ids': [vuln.id], 'tool': updated_tool, "type": type, "_attachments": attachment_data}
3455 res = test_client.patch(self.url(), data=raw_data)
3456 assert res.status_code == 200
3457 assert res.json['updated'] == 0
3458
28673459
28683460 @pytest.mark.usefixtures('logged_user')
28693461 class TestCustomFieldVulnerability(ReadWriteAPITests):
28713463 factory = factories.VulnerabilityFactory
28723464 api_endpoint = 'vulns'
28733465 view_class = VulnerabilityView
2874 patchable_fields = ['description']
3466 patchable_fields = ['name']
28753467
28763468 def test_create_vuln_with_custom_fields_shown(self, test_client, second_workspace, session):
28773469 host = HostFactory.create(workspace=self.workspace)
30383630 assert res.status_code == 400
30393631
30403632 @pytest.mark.usefixtures('ignore_nplusone')
3041 @pytest.mark.skip(reason="To be reimplemented")
30423633 def test_bulk_delete_vuln_id(self, host_with_hostnames, test_client, session):
30433634 """
30443635 This one should only check basic vuln properties
30723663 vuln_count_previous = session.query(Vulnerability).count()
30733664 res_1 = test_client.post(f'/v3/ws/{ws_name}/vulns', data=raw_data_vuln_1)
30743665 res_2 = test_client.post(f'/v3/ws/{ws_name}/vulns', data=raw_data_vuln_2)
3075 vuln_1_id = res_1.json['obj_id']
3076 vuln_2_id = res_2.json['obj_id']
3666 vuln_1_id = int(res_1.json['obj_id'])
3667 vuln_2_id = int(res_2.json['obj_id'])
30773668 vulns_to_delete = [vuln_1_id, vuln_2_id]
3078 request_data = {'vulnerability_ids': vulns_to_delete}
3079 delete_response = test_client.delete(f'/v3/ws/{ws_name}/vulns/bulk_delete', data=request_data)
3669 request_data = {'ids': vulns_to_delete}
3670 delete_response = test_client.delete(f'/v3/ws/{ws_name}/vulns', data=request_data)
30803671 vuln_count_after = session.query(Vulnerability).count()
3081 deleted_vulns = delete_response.json['deleted_vulns']
3672 deleted_vulns = delete_response.json['deleted']
30823673 assert delete_response.status_code == 200
30833674 assert vuln_count_previous == vuln_count_after
30843675 assert deleted_vulns == len(vulns_to_delete)
32313822 assert cmd_obj.object_id == res.json['_id']
32323823 assert res.json['tool'] == command.tool
32333824
3825 def test_custom_field_cvss(self, session, test_client):
3826 add_text_field = CustomFieldsSchemaFactory.create(
3827 table_name='vulnerability',
3828 field_name='cvss',
3829 field_type='text',
3830 field_display_name='CVSS',
3831 )
3832 session.add(add_text_field)
3833 session.commit()
3834
3835 # @pytest.mark.usefixtures('ignore_nplusone')
3836 def test_bulk_delete_by_severity(self, test_client):
3837 all_objs = self.model.query.all()
3838 for obj in all_objs[0:2]:
3839 obj.severity = 'low' # Factory just use "critical" or "high"
3840
3841 data = {"severities": ["low"]}
3842 res = test_client.delete(self.url(), data=data)
3843 assert res.status_code == 200
3844 assert all([was_deleted(obj) for obj in all_objs[0:2]])
3845 assert res.json['deleted'] == 2
3846 assert all([not was_deleted(obj) for obj in all_objs[2:]])
3847 assert self.model.query.count() == 3
3848
32343849 @pytest.mark.parametrize('refs', [
32353850 ('owasp', 'https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet'),
32363851 ('cwe', 'CWE-135'),
32573872 assert ref_name in get_response.json
32583873 assert 1 == len(get_response.json[ref_name])
32593874 assert ref_example == get_response.json[ref_name][0]
3260
3261
3262 @pytest.mark.usefixtures('logged_user')
3263 class TestVulnerabilityCustomFields(ReadWriteAPITests):
3264 model = Vulnerability
3265 factory = factories.VulnerabilityFactory
3266 api_endpoint = 'vulns'
3267 view_class = VulnerabilityView
3268 patchable_fields = ['description']
3269
3270 def test_custom_field_cvss(self, session, test_client):
3271 add_text_field = CustomFieldsSchemaFactory.create(
3272 table_name='vulnerability',
3273 field_name='cvss',
3274 field_type='text',
3275 field_display_name='CVSS',
3276 )
3277 session.add(add_text_field)
3278 session.commit()
32793875
32803876
32813877 @pytest.mark.usefixtures('logged_user')
34324028 session.add(host)
34334029 session.commit()
34344030 paginated_vulns = set()
3435 expected_vulns = set([vuln.id for vuln in vulns])
4031 expected_vulns = {vuln.id for vuln in vulns}
34364032 for offset in range(0, 10):
34374033 query_filter = {
34384034 "filters": [{"name": "severity", "op": "eq", "val": "high"}],
34714067 session.add(host)
34724068 session.commit()
34734069 paginated_vulns = set()
3474 expected_vulns = set([vuln.id for vuln in med_vulns])
4070 expected_vulns = {vuln.id for vuln in med_vulns}
34754071 for offset in range(0, 10):
34764072 query_filter = {
34774073 "filters": [{"name": "severity", "op": "eq", "val": "medium"}],
0 # -*- coding: utf8 -*-
10 '''
21 Faraday Penetration Test IDE
32 Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/)
1110 from faraday.server.api.modules.vulnerability_template import VulnerabilityTemplateView
1211 from tests import factories
1312 from tests.test_api_non_workspaced_base import (
14 ReadWriteAPITests
13 ReadWriteAPITests, BulkUpdateTestsMixin, BulkDeleteTestsMixin
1514 )
1615 from faraday.server.models import (
1716 VulnerabilityTemplate,
4342
4443
4544 @pytest.mark.usefixtures('logged_user')
46 class TestListVulnerabilityTemplateView(ReadWriteAPITests):
45 class TestListVulnerabilityTemplateView(ReadWriteAPITests, BulkUpdateTestsMixin, BulkDeleteTestsMixin):
4746 model = VulnerabilityTemplate
4847 factory = factories.VulnerabilityTemplateFactory
4948 api_endpoint = 'vulnerability_template'
5756 assert res.status_code == 200
5857 assert 'rows' in res.json
5958 for vuln in res.json['rows']:
60 assert set([u'id', u'key', u'value', u'doc']) == set(vuln.keys())
59 assert {'id', 'key', 'value', 'doc'} == set(vuln.keys())
6160 object_properties = [
62 u'exploitation',
63 u'references',
64 u'refs',
65 u'name',
66 u'cwe',
67 u'_rev',
68 u'_id',
69 u'resolution',
70 u'description',
71 u'desc'
61 'exploitation',
62 'references',
63 'refs',
64 'name',
65 'cwe',
66 '_rev',
67 '_id',
68 'resolution',
69 'description',
70 'desc'
7271 ]
7372
7473 expected = set(object_properties)
253252 assert updated_template.severity == raw_data['exploitation']
254253 assert updated_template.resolution == raw_data['resolution']
255254 assert updated_template.description == raw_data['description']
256 assert updated_template.references == set([])
255 assert updated_template.references == set()
257256
258257 @pytest.mark.parametrize('references', [
259258 ',',
273272
274273 def test_update_vulnerabiliy_template_change_refs(self, session, test_client):
275274 template = self.factory.create()
276 for ref_name in set(['old1', 'old2']):
275 for ref_name in {'old1', 'old2'}:
277276 ref = ReferenceTemplateFactory.create(name=ref_name)
278277 self.first_object.reference_template_instances.add(ref)
279278 session.commit()
285284 assert updated_template.severity == raw_data['exploitation']
286285 assert updated_template.resolution == raw_data['resolution']
287286 assert updated_template.description == raw_data['description']
288 assert updated_template.references == set([u'another_ref', u'new_ref'])
287 assert updated_template.references == {'another_ref', 'new_ref'}
289288
290289 def test_create_new_vulnerability_template_with_references(self, session, test_client):
291290 vuln_count_previous = session.query(VulnerabilityTemplate).count()
293292 res = test_client.post('/v3/vulnerability_template', data=raw_data)
294293 assert res.status_code == 201
295294 assert isinstance(res.json['_id'], int)
296 assert set(res.json['refs']) == set(['ref1', 'ref2'])
295 assert set(res.json['refs']) == {'ref1', 'ref2'}
297296 assert vuln_count_previous + 1 == session.query(VulnerabilityTemplate).count()
298297 new_template = session.query(VulnerabilityTemplate).filter_by(id=res.json['_id']).first()
299 assert new_template.references == set([u'ref1', u'ref2'])
298 assert new_template.references == {'ref1', 'ref2'}
300299
301300 def test_delete_vuln_template(self, session, test_client):
302301 template = self.factory.create()
371370
372371 res = test_client.post(self.url(), data=raw_data)
373372 assert res.status_code == 201
374 assert res.json['customfields'] == {u'cvss': u'value'}
373 assert res.json['customfields'] == {'cvss': 'value'}
375374
376375 def test_update_vuln_template_with_custom_fields(self, session, test_client):
377376
403402
404403 res = test_client.put(self.url(template.id), data=raw_data)
405404 assert res.status_code == 200
406 assert res.json['customfields'] == {u'cvss': u'updated value'}
405 assert res.json['customfields'] == {'cvss': 'updated value'}
407406
408407 vuln_template = session.query(VulnerabilityTemplate).filter_by(id=template.id).first()
409 assert vuln_template.custom_fields == {u'cvss': u'updated value'}
408 assert vuln_template.custom_fields == {'cvss': 'updated value'}
410409
411410 def test_add_vuln_template_from_csv(self, session, test_client, csrf_token):
412411 expected_created_vuln_template = 1
609608 assert res.json['vulns_with_conflict'][1][1] == vuln_2['name']
610609
611610 assert len(res.json['vulns_created']) == 0
611
612 def test_bulk_delete_vulnerabilities_template(self, test_client, session):
613 previous_count = session.query(VulnerabilityTemplate).count()
614 vuln_template_1 = VulnerabilityTemplate(name='vuln_1', severity='high')
615 session.add(vuln_template_1)
616 vuln_template_2 = VulnerabilityTemplate(name='vuln_2', severity='high')
617 session.add(vuln_template_2)
618 vuln_template_3 = VulnerabilityTemplate(name='vuln_3', severity='high')
619 session.add(vuln_template_3)
620 session.commit()
621
622 data = {'ids': [vuln_template_1.id, vuln_template_2.id, vuln_template_3.id]}
623 res = test_client.delete(self.url(), data=data)
624 assert res.status_code == 200
625 assert res.json['deleted'] == 3
626 assert previous_count == session.query(VulnerabilityTemplate).count()
33 See the file 'doc/LICENSE' for the license information
44
55 '''
6
6 from datetime import date
77 import time
8 from urllib.parse import urljoin
9
810 import pytest
9 from posixpath import join as urljoin
10
11 from faraday.server.models import Workspace, Scope
11 from posixpath import join
12
13 from faraday.server.models import Workspace, Scope, SeveritiesHistogram
1214 from faraday.server.api.modules.workspaces import WorkspaceView
13 from tests.test_api_non_workspaced_base import ReadWriteAPITests
15 from tests.test_api_non_workspaced_base import ReadWriteAPITests, BulkDeleteTestsMixin
1416 from tests import factories
1517
1618
17 class TestWorkspaceAPI(ReadWriteAPITests):
19 class TestWorkspaceAPI(ReadWriteAPITests, BulkDeleteTestsMixin):
1820 model = Workspace
1921 factory = factories.WorkspaceFactory
2022 api_endpoint = 'ws'
2123 lookup_field = 'name'
2224 view_class = WorkspaceView
23 patchable_fields = ['name']
25 patchable_fields = ['description']
2426
2527 @pytest.mark.usefixtures('ignore_nplusone')
2628 def test_filter_restless_by_name(self, test_client):
2729 res = test_client.get(
28 urljoin(
30 join(
2931 self.url(),
3032 f'filter?q={{"filters":[{{"name": "name", "op":"eq", "val": "{self.first_object.name}"}}]}}'
3133 )
3739 @pytest.mark.usefixtures('ignore_nplusone')
3840 def test_filter_restless_by_name_zero_results_found(self, test_client):
3941 res = test_client.get(
40 urljoin(
42 join(
4143 self.url(),
4244 'filter?q={"filters":[{"name": "name", "op":"eq", "val": "thiswsdoesnotexist"}]}'
4345 )
4850 def test_filter_restless_by_description(self, test_client):
4951 self.first_object.description = "this is a new description"
5052 res = test_client.get(
51 urljoin(
53 join(
5254 self.url(),
5355 f'filter?q={{"filters":[{{"name": "description", "op":"eq", "val": "{self.first_object.description}"}}'
5456 ']}'
8486
8587 self.first_object.description = "this is a new description"
8688 res = test_client.get(
87 urljoin(
89 join(
8890 self.url(),
8991 f'filter?q={{"filters":[{{"name": "description", "op":"eq", "val": "{self.first_object.description}"}}'
9092 ']}'
135137
136138 session.add_all(vulns)
137139 session.commit()
138 res = test_client.get(self.url(self.first_object) + querystring)
140 res = test_client.get(urljoin(self.url(self.first_object), querystring))
139141 assert res.status_code == 200
140142 assert res.json['stats']['code_vulns'] == 0
141143 assert res.json['stats']['web_vulns'] == 2
147149 assert res.json['stats']['opened_vulns'] == 10
148150 assert res.json['stats']['confirmed_vulns'] == 2
149151
152 @pytest.mark.skip_sql_dialect('sqlite')
153 @pytest.mark.usefixtures('ignore_nplusone')
154 def test_histogram(self,
155 vulnerability_factory,
156 vulnerability_web_factory,
157 second_workspace,
158 test_client,
159 session):
160
161 session.query(SeveritiesHistogram).delete()
162 session.commit()
163
164 vulns = vulnerability_factory.create_batch(8, workspace=self.first_object,
165 confirmed=False, status='open', severity='critical')
166
167 vulns += vulnerability_factory.create_batch(3, workspace=self.first_object,
168 confirmed=True, status='open', severity='high')
169
170 vulns += vulnerability_web_factory.create_batch(2, workspace=second_workspace,
171 confirmed=True, status='open', severity='medium')
172
173 vulns += vulnerability_web_factory.create_batch(2, workspace=second_workspace,
174 confirmed=True, status='open', severity='low')
175
176 session.add_all(vulns)
177 session.commit()
178 res = test_client.get('/v3/ws?histogram=true')
179 assert res.status_code == 200
180 firs_ws = [ws['histogram'] for ws in res.json if ws['name'] == self.first_object.name]
181 assert len(firs_ws[0]) == 20
182 ws_histogram = firs_ws[0]
183 for ws_date in ws_histogram:
184 if ws_date['date'] == date.today().strftime("%Y-%m-%d"):
185 assert ws_date['medium'] == 0
186 assert ws_date['high'] == 3
187 assert ws_date['critical'] == 8
188 assert ws_date['confirmed'] == 3
189 else:
190 assert ws_date['medium'] == 0
191 assert ws_date['high'] == 0
192 assert ws_date['critical'] == 0
193 assert ws_date['confirmed'] == 0
194
195 second_ws = [ws['histogram'] for ws in res.json if ws['name'] == second_workspace.name]
196 assert len(second_ws[0]) == 20
197 ws_histogram = second_ws[0]
198 for ws_date in ws_histogram:
199 if ws_date['date'] == date.today().strftime("%Y-%m-%d"):
200 assert ws_date['medium'] == 2
201 assert ws_date['high'] == 0
202 assert ws_date['critical'] == 0
203 assert ws_date['confirmed'] == 2
204 else:
205 assert ws_date['medium'] == 0
206 assert ws_date['high'] == 0
207 assert ws_date['critical'] == 0
208 assert ws_date['confirmed'] == 0
209
210 res = test_client.get('/v3/ws?histogram=True&histogram_days=a')
211 assert res.status_code == 200
212 firs_ws = [ws['histogram'] for ws in res.json if ws['name'] == self.first_object.name]
213 assert len(firs_ws[0]) == 20
214
215 res = test_client.get('/v3/ws?histogram=true&histogram_days=[asdf, "adsf"]')
216 assert res.status_code == 200
217 firs_ws = [ws['histogram'] for ws in res.json if ws['name'] == self.first_object.name]
218 assert len(firs_ws[0]) == 20
219
220 res = test_client.get('/v3/ws?histogram=true&histogram_days=[asdf, "adsf"]')
221 assert res.status_code == 200
222 firs_ws = [ws['histogram'] for ws in res.json if ws['name'] == self.first_object.name]
223 assert len(firs_ws[0]) == 20
224
225 res = test_client.get('/v3/ws?histogram=true&histogram_days=5')
226 assert res.status_code == 200
227 firs_ws = [ws['histogram'] for ws in res.json if ws['name'] == self.first_object.name]
228 assert len(firs_ws[0]) == 5
229
230 res = test_client.get('/v3/ws?histogram=true&histogram_days=365')
231 assert res.status_code == 200
232 firs_ws = [ws['histogram'] for ws in res.json if ws['name'] == self.first_object.name]
233 assert len(firs_ws[0]) == 365
234
235 res = test_client.get('/v3/ws?histogram=asdf&histogram_days=365')
236 assert res.status_code == 200
237 for ws in res.json:
238 assert 'histogram' not in ws
239
150240 @pytest.mark.parametrize('querystring', [
151241 '?status=closed'
152242 ])
167257
168258 session.add_all(vulns)
169259 session.commit()
170 res = test_client.get(self.url(self.first_object) + querystring)
260 res = test_client.get(urljoin(self.url(self.first_object), querystring))
171261 assert res.status_code == 200
172262 assert res.json['stats']['code_vulns'] == 0
173263 assert res.json['stats']['web_vulns'] == 0
201291
202292 session.add_all(vulns)
203293 session.commit()
204 res = test_client.get(self.url(self.first_object) + querystring)
294 res = test_client.get(urljoin(self.url(self.first_object), querystring))
205295 assert res.status_code == 200
206296 assert res.json['stats']['code_vulns'] == 0
207297 assert res.json['stats']['web_vulns'] == 2
223313 confirmed=True)
224314 session.add_all(vulns)
225315 session.commit()
226 res = test_client.get(self.url(self.first_object) + querystring)
316 res = test_client.get(urljoin(self.url(self.first_object), querystring))
227317 assert res.status_code == 200
228318 assert res.json['stats']['total_vulns'] == 5
229319
354444 assert res.status_code == 201
355445 assert set(res.json['scope']) == set(desired_scope)
356446 workspace = Workspace.query.get(res.json['id'])
357 assert set(s.name for s in workspace.scope) == set(desired_scope)
447 assert {s.name for s in workspace.scope} == set(desired_scope)
358448
359449 def test_update_with_scope(self, session, test_client, workspace):
360450 session.add(Scope(name='test.com', workspace=workspace))
368458 res = test_client.put(self.url(obj=workspace), data=raw_data)
369459 assert res.status_code == 200
370460 assert set(res.json['scope']) == set(desired_scope)
371 assert set(s.name for s in workspace.scope) == set(desired_scope)
372
373 @pytest.mark.skip # TODO fix fox sqlite
374 def test_list_retrieves_all_items_from(self, test_client):
375 super().test_list_retrieves_all_items_from(test_client)
461 assert {s.name for s in workspace.scope} == set(desired_scope)
462
463 @pytest.mark.skip_sql_dialect('sqlite')
464 def test_list_retrieves_all_items_from(self, test_client, logged_user):
465 super().test_list_retrieves_all_items_from(test_client, logged_user)
376466
377467 def test_workspace_activation(self, test_client, workspace, session):
378468 workspace.active = False
0 # -*- coding: utf8 -*-
10 '''
21 Faraday Penetration Test IDE
32 Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/)
43 See the file 'doc/LICENSE' for the license information
54
65 '''
7 from builtins import str
8 from posixpath import join as urljoin
6 from posixpath import join
97
108 """Generic tests for APIs prefixed with a workspace_name"""
119
4947
5048 def url(self, obj=None, workspace=None):
5149 workspace = workspace or self.workspace
52 url = API_PREFIX + workspace.name + '/' + self.api_endpoint
50 url = join(API_PREFIX + workspace.name, self.api_endpoint)
5351 if obj is not None:
5452 id_ = str(obj.id) if isinstance(
5553 obj, self.model) else str(obj)
56 url += '/' + id_
54 url = join(url, id_)
5755 return url
5856
5957
102100 res = test_client.get(self.url(self.first_object, second_workspace))
103101 assert res.status_code == 404
104102
105 @pytest.mark.parametrize('object_id', [123456789, -1, 'xxx', u'áá'])
103 @pytest.mark.parametrize('object_id', [123456789, -1, 'xxx', 'áá'])
106104 def test_404_when_retrieving_unexistent_object(self, test_client,
107105 object_id):
108106 url = self.url(object_id)
203201 def test_update_an_object_readonly_fails(self, test_client, method):
204202 self.workspace.readonly = True
205203 db.session.commit()
206 for unique_field in self.unique_fields:
207 data = self.factory.build_dict()
208 old_field = getattr(self.objects[0], unique_field)
209 old_id = getattr(self.objects[0], 'id')
210 if method == "PUT":
211 res = test_client.put(self.url(self.first_object), data=data)
212 elif method == "PATCH":
213 res = test_client.patch(self.url(self.first_object), data=data)
214 db.session.commit()
215 assert res.status_code == 403
216 assert self.model.query.count() == OBJECT_COUNT
217 assert old_field == getattr(self.model.query.filter(self.model.id == old_id).one(), unique_field)
204
205 data = self.factory.build_dict(workspace=self.workspace)
206 count = self.model.query.count()
207 if method == "PUT":
208 res = test_client.put(self.url(self.first_object),
209 data=data)
210 elif method == "PATCH":
211 res = test_client.patch(self.url(self.first_object),
212 data=data)
213 assert res.status_code == 403
214 assert self.model.query.count() == count
218215
219216 @pytest.mark.parametrize("method", ["PUT", "PATCH"])
220217 def test_update_inactive_fails(self, test_client, method):
271268 assert object_id == expected_id
272269
273270
271 @pytest.mark.usefixtures('logged_user')
272 class BulkUpdateTestsMixin:
273
274 @staticmethod
275 def control_data(test_suite, data: dict) -> dict:
276 return {
277 key: value for (key, value) in data.items()
278 if key in UpdateTestsMixin.control_patcheable_data(test_suite, data)
279 and key not in test_suite.unique_fields
280 }
281
282 def get_all_objs_and_ids(self):
283 all_objs = self.model.query.all()
284 all_objs_id = [obj.__getattribute__(self.view_class.lookup_field) for obj in all_objs]
285 return all_objs, all_objs_id
286
287 def test_bulk_update_an_object(self, test_client, logged_user):
288 all_objs = self.model.query.all()
289 all_objs_id = [obj.__getattribute__(self.view_class.lookup_field) for obj in self.model.query.all()]
290 all_objs, all_objs_id = all_objs[:-1], all_objs_id[:-1]
291
292 data = self.factory.build_dict(workspace=self.workspace)
293 data = self.control_cant_change_data(data)
294 count = self.model.query.count()
295 data = BulkUpdateTestsMixin.control_data(self, data)
296
297 res = test_client.patch(self.url(), data={})
298 assert res.status_code == 400
299 data["ids"] = all_objs_id
300 res = test_client.patch(self.url(), data=data)
301
302 assert res.status_code == 200, (res.status_code, res.json)
303 assert self.model.query.count() == count
304 assert res.json['updated'] == len(all_objs)
305 for obj in self.model.query.all():
306 if getattr(obj, self.view_class.lookup_field) not in all_objs_id:
307 assert any(
308 [
309 data[updated_field] != getattr(obj, updated_field)
310 for updated_field in data if updated_field != 'ids'
311 ]
312 )
313 else:
314 assert all(
315 [
316 data[updated_field] == getattr(obj, updated_field)
317 for updated_field in data if updated_field != 'ids'
318 ]
319 )
320
321 def test_bulk_update_an_object_readonly_fails(self, test_client):
322 self.workspace.readonly = True
323 db.session.commit()
324 all_objs, all_objs_id = self.get_all_objs_and_ids()
325 data = self.factory.build_dict(workspace=self.workspace)
326 data = self.control_cant_change_data(data)
327 data = BulkUpdateTestsMixin.control_data(self, data)
328 count = self.model.query.count()
329 data["ids"] = all_objs_id
330 res = test_client.patch(self.url(), data=data)
331 assert res.status_code == 403
332 assert self.model.query.count() == count
333
334 def test_bulk_update_inactive_fails(self, test_client):
335 self.workspace.deactivate()
336 db.session.commit()
337 all_objs, all_objs_id = self.get_all_objs_and_ids()
338 data = self.factory.build_dict(workspace=self.workspace)
339 data = self.control_cant_change_data(data)
340 data = BulkUpdateTestsMixin.control_data(self, data)
341 count = self.model.query.count()
342 data["ids"] = all_objs_id
343 res = test_client.patch(self.url(), data=data)
344 assert res.status_code == 403
345 assert self.model.query.count() == count
346
347 @pytest.mark.parametrize('existing', (True, False))
348 def test_bulk_update_fails_with_repeated_unique(self, test_client, session, existing):
349 for unique_field in self.unique_fields:
350 data = self.factory.build_dict()
351 if existing:
352 data[unique_field] = getattr(self.objects[3], unique_field)
353 data["ids"] = [getattr(self.objects[0], self.view_class.lookup_field)]
354 else:
355 data["ids"] = [getattr(self.objects[i], self.view_class.lookup_field) for i in range(0, 2)]
356 res = test_client.patch(self.url(), data=data)
357 assert res.status_code == 409
358 assert self.model.query.count() == OBJECT_COUNT
359
360 def test_bulk_update_cant_change_id(self, test_client):
361 raw_json = self.factory.build_dict(workspace=self.workspace)
362 raw_json = self.control_cant_change_data(raw_json)
363 raw_json['id'] = 100000
364 expected_id = self.first_object.id
365 raw_json["ids"] = [expected_id]
366 res = test_client.patch(self.url(), data=raw_json)
367 assert res.status_code == 200, (res.status_code, res.data)
368 assert self.model.query.filter(self.model.id == 100000).first() is None
369
370 def test_patch_bulk_update_an_object_does_not_fail_with_partial_data(self, test_client, logged_user):
371 """To do this the user should use a PATCH request"""
372 all_objs_id = [obj.__getattribute__(self.view_class.lookup_field) for obj in self.model.query.all()]
373 res = test_client.patch(self.url(), data={"ids": all_objs_id})
374 assert res.status_code == 200, (res.status_code, res.json)
375
376 def test_bulk_update_invalid_ids(self, test_client):
377 data = self.factory.build_dict(workspace=self.workspace)
378 data = BulkUpdateTestsMixin.control_data(self, data)
379 data['ids'] = [-1, 'test']
380 res = test_client.patch(self.url(), data=data)
381 assert res.status_code == 200
382 assert res.json['updated'] == 0
383 data['ids'] = [-1, 'test', self.first_object.__getattribute__(self.view_class.lookup_field)]
384 res = test_client.patch(self.url(), data=data)
385 assert res.status_code == 200
386 assert res.json['updated'] == 1
387
388 def test_bulk_update_wrong_content_type(self, test_client):
389 all_objs = self.model.query.all()
390 all_objs_id = [obj.__getattribute__(self.view_class.lookup_field) for obj in all_objs]
391
392 request_data = {'ids': all_objs_id}
393 headers = [('content-type', 'text/xml')]
394
395 res = test_client.patch(self.url(), data=request_data, headers=headers)
396 assert res.status_code == 400
397
398
274399 class CountTestsMixin:
275400 def test_count(self, test_client, session, user_factory):
276401
287412
288413 session.commit()
289414
290 res = test_client.get(urljoin(self.url(), "count?group_by=creator_id"))
415 res = test_client.get(join(self.url(), "count?group_by=creator_id"))
291416
292417 assert res.status_code == 200, res.json
293418 res = res.get_json()
317442
318443 session.commit()
319444
320 res = test_client.get(urljoin(self.url(), "count?group_by=creator_id&order=desc"))
445 res = test_client.get(join(self.url(), "count?group_by=creator_id&order=desc"))
321446
322447 assert res.status_code == 200, res.json
323448 res = res.get_json()
366491 assert self.model.query.count() == OBJECT_COUNT
367492
368493
494 @pytest.mark.usefixtures('logged_user')
495 class BulkDeleteTestsMixin:
496
497 def get_all_objs_and_ids(self):
498 all_objs = self.model.query.all()
499 all_objs_id = [obj.__getattribute__(self.view_class.lookup_field) for obj in all_objs]
500 return all_objs, all_objs_id
501
502 @pytest.mark.usefixtures('ignore_nplusone')
503 def test_bulk_delete(self, test_client):
504 all_objs, all_objs_id = self.get_all_objs_and_ids()
505 ignored_obj = all_objs[-1]
506 all_objs, all_objs_id = all_objs[:-1], all_objs_id[:-1]
507
508 res = test_client.delete(self.url(), data={})
509 assert res.status_code == 400
510 data = {"ids": all_objs_id}
511 res = test_client.delete(self.url(), data=data)
512 assert res.status_code == 200
513 assert all([was_deleted(obj) for obj in all_objs])
514 assert res.json['deleted'] == len(all_objs)
515 assert not was_deleted(ignored_obj)
516 assert self.model.query.count() == 1
517
518 def test_bulk_delete_invalid_ids(self, test_client):
519 request_data = {'ids': [-1, 'test']}
520 count = self.model.query.count()
521 res = test_client.delete(self.url(), data=request_data)
522 assert res.status_code == 200
523 assert res.json['deleted'] == 0
524 assert self.model.query.count() == count
525
526 def test_bulk_delete_wrong_content_type(self, test_client):
527 all_objs = self.model.query.all()
528 all_objs_id = [obj.__getattribute__(self.view_class.lookup_field) for obj in all_objs]
529 count = self.model.query.count()
530
531 request_data = {'ids': all_objs_id}
532 headers = [('content-type', 'text/xml')]
533
534 res = test_client.delete(self.url(), data=request_data, headers=headers)
535 assert res.status_code == 400
536 assert self.model.query.count() == count
537 assert all([not was_deleted(obj) for obj in all_objs])
538
539 def test_bulk_delete_readonly_fails(self, test_client, session):
540 self.workspace.readonly = True
541 session.commit()
542 all_objs, all_objs_id = self.get_all_objs_and_ids()
543 data = {"ids": all_objs_id}
544 res = test_client.delete(self.url(), data=data)
545 assert res.status_code == 403 # No content
546 assert not any([was_deleted(obj) for obj in all_objs])
547 assert self.model.query.count() == OBJECT_COUNT
548
549 def test_delete_inactive_fails(self, test_client):
550 self.workspace.deactivate()
551 db.session.commit()
552 all_objs, all_objs_id = self.get_all_objs_and_ids()
553 data = {"ids": all_objs_id}
554 res = test_client.delete(self.url(), data=data)
555 assert res.status_code == 403 # No content
556 assert not any([was_deleted(obj) for obj in all_objs])
557 assert self.model.query.count() == OBJECT_COUNT
558
559 def test_delete_from_other_workspace_fails(self, test_client):
560 all_objs, all_objs_id = self.get_all_objs_and_ids()
561
562 data = {"ids": all_objs_id + [10000000]}
563 res = test_client.delete(self.url(), data=data)
564 assert res.status_code == 204
565 assert all([was_deleted(obj) for obj in all_objs])
566 assert res.json['deleted'] == len(all_objs)
567 assert self.model.query.count() == 0
568
569
369570 class PaginationTestsMixin(OriginalPaginationTestsMixin):
370571 def create_many_objects(self, session, n):
371572 objects = self.factory.create_batch(n, workspace=self.workspace)
0
10 import os
21 import subprocess
32
4241 print(std)
4342 print(err)
4443 assert subproc.returncode == 0, ('manage migrate failed!', std, err)
45
46
47 # I'm Py3
6767
6868 with pytest.raises(AssertionError):
6969 session.commit()
70
71
72 # I'm Py3
0
10 import json
21
32 import pytest
9090
9191 if __name__ == '__main__':
9292 unittest.main()
93
94 # I'm Py3
1717 )
1818
1919 UNIQUE_FIELDS = {
20 License: [u'product', u'start_date', u'end_date'],
21 Service: [u'port', u'protocol', u'host_id', u'workspace_id'],
22 Host: [u'ip', u'workspace_id'],
20 License: ['product', 'start_date', 'end_date'],
21 Service: ['port', 'protocol', 'host_id', 'workspace_id'],
22 Host: ['ip', 'workspace_id'],
2323 Vulnerability: [
2424 'name',
2525 'description',
6868 unique_constraints = get_unique_fields(session, object_)
6969 for unique_constraint in unique_constraints:
7070 assert unique_constraint == expected_unique_fields
71
72 # I'm Py3
0
10 import pytest
21 from faraday.server.models import Agent, Executor
32 from faraday.server.websocket_factories import WorkspaceServerFactory, \