Commit fb64ee38 authored by Lorenzo Faletra's avatar Lorenzo Faletra

Import Upstream version 3.6.0

parent a048c467
Pipeline #163 failed with stages
in 0 seconds
...@@ -69,3 +69,10 @@ uml_schema.png ...@@ -69,3 +69,10 @@ uml_schema.png
# Documentation builds # Documentation builds
doc/_build/ doc/_build/
scripts/searcher/log/searcher.log
=0.54.0
stream.svg
scripts/searcher/output/searcher.db
.pytest_cache
reports/executive/outputs/scope.docx
.gitignore
variables:
TZ: "America/New_York"
# Configure postgres service (https://hub.docker.com/_/postgres/)
POSTGRES_DB: custom_db
POSTGRES_USER: custom_user
POSTGRES_PASSWORD: custom_pass
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
cache:
paths:
- .cache/pip
stages:
- pre_testing
- testing
- post_testing
services:
- postgres:latest
closure_compiler:
image:
name: jborza/closure-compiler
entrypoint: ["/bin/sh", "-c"]
stage: pre_testing
script:
- /opt/cc.sh server/www/scripts
merge_conflict_check:
image: python:3
stage: pre_testing
allow_failure: true
script:
- git config --global user.email "you@example.com"
- git config --global user.name "Mergerbot"
- python3 merge-conflict-detector.py
pylint:
image: registry.gitlab.com/faradaysec/faraday/faraday_testing_base # I just need an image with python-dev and python-pip
stage: pre_testing
script:
- pip install pylint anybadge
- pylint server |tee pylint.txt || true
- score=$(sed -n 's/^Your code has been rated at \([-0-9.]*\)\/.*/\1/p' pylint.txt)
- anybadge --label pylint --value=$score --file pylint.svg 4=red 6=orange 8=yellow 10=green
artifacts:
paths:
- pylint.svg
postgresql_test:
image: registry.gitlab.com/faradaysec/faraday/faraday_testing_base
stage: testing
coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/'
script:
- pip install virtualenv
- virtualenv -p python2 faraday_venv
- source faraday_venv/bin/activate
- pip install --upgrade -r requirements_server.txt
- pip install --upgrade responses pytest-xdist pytest-cov
- pip install --upgrade -r requirements_dev.txt
- mkdir -p ~/.faraday/config
- cp test_cases/data/server.ini ~/.faraday/config
- sed -i 's/mapped_table/persist_selectable/' faraday_venv/lib/python2.7/site-packages/flask_sqlalchemy/__init__.py # TODO remove when flask_sqlalchemy fixes the issue
- pytest test_cases -v --cov=server --connection-string=postgresql+psycopg2://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres/$POSTGRES_DB
artifacts:
when: on_failure
paths:
- ~/.faraday/logs/faraday-server.log
sqlite_test:
image: registry.gitlab.com/faradaysec/faraday/faraday_testing_base
stage: testing
coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/'
script:
- pip install virtualenv
- virtualenv -p python2 faraday_venv
- source faraday_venv/bin/activate
- pip install --upgrade -r requirements_server.txt
- pip install --upgrade responses pytest-xdist pytest-cov
- pip install --upgrade -r requirements_dev.txt
- mkdir -p ~/.faraday/config
- cp test_cases/data/server.ini ~/.faraday/config
- sed -i 's/mapped_table/persist_selectable/' faraday_venv/lib/python2.7/site-packages/flask_sqlalchemy/__init__.py # TODO remove when flask_sqlalchemy fixes the issue
- pytest test_cases -v --cov=server --color=yes
generate_release_file:
image: python:3
stage: post_testing
allow_failure: true
script:
- apt-get update -qy
- apt-get install -y python-dev python-pip
- pip install packaging
- cd CHANGELOG && python3 changelog.py
artifacts:
paths:
- CHANGELOG/RELEASE.md
only:
variables:
- $FULL_DOC == "True"
- $RELEASE_FILE == "True"
- $CI_COMMIT_REF_NAME =~ /^.*\/(dev|master)$/
# This is a test of future test case that will be scheduled
i_do_nothing:
image: python:3
stage: post_testing
allow_failure: true
script:
- apt-get update -qy
- apt-get install -y python-dev python-pip
only:
variables:
- $FULL_TEST == "True"
- $I_DO_NOTH_TEST == "True"
# This is a test of future test case that will be scheduled
i_do_nothing2:
image: python:3
stage: post_testing
allow_failure: true
script:
- apt-get update -qy
- apt-get install -y python-dev python-pip
only:
variables:
- $FULL_TEST == "True"
- $I_DO_NOTH2_TEST == "True"
---
name: Feature request
about: Suggest an idea for this
---
**What's the problem this feature will solve?**
<!-- What are you trying to do, that you are unable to achieve with faraday as it currently stands? -->
**Describe the solution you'd like**
<!-- Clear and concise description of what you want to happen. -->
<!-- Provide examples of real world use cases that this would enable and how it solves the problem described above. -->
**Alternative Solutions**
<!-- different approach to solving this issue? Please elaborate here. -->
**Additional context**
<!-- Add any other context, links, etc. about the feature here. -->
---
name: Bug report
about: Create a report an issue
---
Please search the [Wiki](https://github.com/infobyte/faraday/wiki) for a solution before posting a ticket. Use the <strong>“New Support Request”</strong> button to the right of the screen to submit a ticket for technical support.
## Issue Type
- Bug Re port
- Feature Idea
- Documentation Report
## Faraday version
Paste the output of the *./faraday.py --version* command
## Component Name
If you know where the problem lays indicate it:
WebGui/GTKGui/Plugin/Console/Continuous Scanning/Etc.
## Steps to reproduce
Provide detailed steps on how the issue happened so we can try to reproduce it. If the issue is random, please provide as much information as possible.
## Expected results
What did you expect to happen when following the steps above?
### Debugging tracebacks (current results)
Try to reproduce the bug with the server and/or gtk client in debug mode and check the logs for the ERROR string.
Add here any errors you find while running in debug mode or, if possible, Faraday’s log files (located at *$HOME/.faraday/logs/*).
If you need help on how to execute in debug mode [click here for more information](https://github.com/infobyte/faraday/wiki/troubleshooting).
Please attach the result of:
pip freeze > requirements_freeze.txt
### Screenshots
If you don't find anything on the logs, please provide screenshots of the error.
## Environment information
### Configuration files
Mention any settings you have changed/added/removed.
### Reports/Extra data
If you are having issues with plugins, please attach relevant files if possible.
(strip your reports of all sensitive information beforehand).
### OS
Provide information on your operating system. Example:
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.10
DISTRIB_CODENAME=yakkety
DISTRIB_DESCRIPTION="Ubuntu 16.10"
* Fix CSRF (Cross-Site Request Forgery) vulnerability in vulnerability attachments API.
This allowed an attacker to upload evidence to vulns. He/she required to know the
desired workspace name and vulnerability id so it complicated the things a bit. We
classified this vuln as a low impact one.
* Readonly and disabled workspaces
* Add fields 'impact', 'easeofresolution' and 'policyviolations' to vulnerability_template
* Add pagination in 'Command history', 'Last Vulnerabilities', 'Activity logs' into dashboard
* Add status_code field to web vulnerability
* Preserve selection after bulk edition of vulnerabilities in the Web UI
* Faraday's database will be created using UTF-8 encoding
* Fix bug of "select a different workspace" from an empty list loop.
* Fix bug when creating duplicate custom fields
* Fix bug when loading in server.ini with extra configs
* Fix `./manage.py command`. It wasn't working since the last schema migration
* `./manage.py createsuperuser` command renamed to `./manage.py create-superuser`
* Fix bug when non-numeric vulnerability IDs were passed to the attachments API
* Fix logic in search exploits
* Add ability to 'Searcher' to execute rules in loop with dynamic variables
* Send searcher alert with custom mail
* Add gitlab-ci.yml file to execute test and pylint on gitlab runner
* Fix 500 error when updating services and vulns with specific read-only parameters set
* Fix SQLMap plugin to support newer versions of the tool
* Improve service's parser for Lynis plugin
* Fix bug when parsing URLs in Acunetix reports
* Fix and update NetSparker Plugin
* Fix bug in nessus plugin. It was trying to create a host without IP. Enabled logs on the server for plugin processing (use --debug)
* Fix bug when parsing hostnames in Nessus reports
* Fix SSLyze report automatic detection, so reports can be imported from the web ui
* Update Dnsmap Plugin
...@@ -9,6 +9,38 @@ New features in the latest update ...@@ -9,6 +9,38 @@ New features in the latest update
===================================== =====================================
3.6 [Feb 21th, 2019]:
---
* Fix CSRF (Cross-Site Request Forgery) vulnerability in vulnerability attachments API.
This allowed an attacker to upload evidence to vulns. He/she required to know the
desired workspace name and vulnerability id so it complicated the things a bit. We
classified this vuln as a low impact one.
* Readonly and disabled workspaces
* Add fields 'impact', 'easeofresolution' and 'policyviolations' to vulnerability_template
* Add pagination in 'Command history', 'Last Vulnerabilities', 'Activity logs' into dashboard
* Add status_code field to web vulnerability
* Preserve selection after bulk edition of vulnerabilities in the Web UI
* Faraday's database will be created using UTF-8 encoding
* Fix bug of "select a different workspace" from an empty list loop.
* Fix bug when creating duplicate custom fields
* Fix bug when loading in server.ini with extra configs
* Fix `./manage.py command`. It wasn't working since the last schema migration
* `./manage.py createsuperuser` command renamed to `./manage.py create-superuser`
* Fix bug when non-numeric vulnerability IDs were passed to the attachments API
* Fix logic in search exploits
* Add ability to 'Searcher' to execute rules in loop with dynamic variables
* Send searcher alert with custom mail
* Add gitlab-ci.yml file to execute test and pylint on gitlab runner
* Fix 500 error when updating services and vulns with specific read-only parameters set
* Fix SQLMap plugin to support newer versions of the tool
* Improve service's parser for Lynis plugin
* Fix bug when parsing URLs in Acunetix reports
* Fix and update NetSparker Plugin
* Fix bug in nessus plugin. It was trying to create a host without IP. Enabled logs on the server for plugin processing (use --debug)
* Fix bug when parsing hostnames in Nessus reports
* Fix SSLyze report automatic detection, so reports can be imported from the web ui
* Update Dnsmap Plugin
3.5 [Jan 16th, 2019]: 3.5 [Jan 16th, 2019]:
--- ---
* Redesgin of new/edit vulnerability forms * Redesgin of new/edit vulnerability forms
......
...@@ -9,6 +9,38 @@ New features in the latest update ...@@ -9,6 +9,38 @@ New features in the latest update
===================================== =====================================
3.6 [Feb 21th, 2019]:
---
* Fix CSRF (Cross-Site Request Forgery) vulnerability in vulnerability attachments API.
This allowed an attacker to upload evidence to vulns. He/she required to know the
desired workspace name and vulnerability id so it complicated the things a bit. We
classified this vuln as a low impact one.
* Readonly and disabled workspaces
* Add fields 'impact', 'easeofresolution' and 'policyviolations' to vulnerability_template
* Add pagination in 'Command history', 'Last Vulnerabilities', 'Activity logs' into dashboard
* Add status_code field to web vulnerability
* Preserve selection after bulk edition of vulnerabilities in the Web UI
* Faraday's database will be created using UTF-8 encoding
* Fix bug of "select a different workspace" from an empty list loop.
* Fix bug when creating duplicate custom fields
* Fix bug when loading in server.ini with extra configs
* Fix `./manage.py command`. It wasn't working since the last schema migration
* `./manage.py createsuperuser` command renamed to `./manage.py create-superuser`
* Fix bug when non-numeric vulnerability IDs were passed to the attachments API
* Fix logic in search exploits
* Add ability to 'Searcher' to execute rules in loop with dynamic variables
* Send searcher alert with custom mail
* Add gitlab-ci.yml file to execute test and pylint on gitlab runner
* Fix 500 error when updating services and vulns with specific read-only parameters set
* Fix SQLMap plugin to support newer versions of the tool
* Improve service's parser for Lynis plugin
* Fix bug when parsing URLs in Acunetix reports
* Fix and update NetSparker Plugin
* Fix bug in nessus plugin. It was trying to create a host without IP. Enabled logs on the server for plugin processing (use --debug)
* Fix bug when parsing hostnames in Nessus reports
* Fix SSLyze report automatic detection, so reports can be imported from the web ui
* Update Dnsmap Plugin
3.5 [Jan 16th, 2019]: 3.5 [Jan 16th, 2019]:
--- ---
* Redesgin of new/edit vulnerability forms * Redesgin of new/edit vulnerability forms
......
...@@ -261,7 +261,7 @@ def main(workspace="", args=None, parser=None): ...@@ -261,7 +261,7 @@ def main(workspace="", args=None, parser=None):
try: try:
models.create_host(WORKSPACE, host) models.create_host(WORKSPACE, host)
except Exception as ex: except Exception as ex:
import ipdb; ipdb.set_trace() print(ex)
host = models.get_host(WORKSPACE, ip=host.getName()) host = models.get_host(WORKSPACE, ip=host.getName())
if service is not None: if service is not None:
......
...@@ -64,6 +64,7 @@ CONST_PLUGIN_SETTINGS = "plugin_settings" ...@@ -64,6 +64,7 @@ CONST_PLUGIN_SETTINGS = "plugin_settings"
DEFAULT_XML = os.path.dirname(__file__) + "/default.xml" DEFAULT_XML = os.path.dirname(__file__) + "/default.xml"
DEFAULT_SERVER_INI = os.path.join(os.path.dirname(__file__), "..", "server", "default.ini")
class Configuration: class Configuration:
...@@ -648,12 +649,21 @@ class Configuration: ...@@ -648,12 +649,21 @@ class Configuration:
def getInstanceConfiguration(): def getInstanceConfiguration():
global the_config global the_config
if the_config is None: if the_config is None:
faraday_dir = os.path.expanduser("~/.faraday")
if not os.path.exists(faraday_dir):
os.mkdir(faraday_dir)
config_dir = os.path.expanduser("~/.faraday/config") config_dir = os.path.expanduser("~/.faraday/config")
if not os.path.exists(config_dir): if not os.path.exists(config_dir):
os.mkdir(config_dir) os.mkdir(config_dir)
faraday_server_config = os.path.expanduser("~/.faraday/config/server.ini")
if not os.path.isfile(faraday_server_config):
shutil.copy(DEFAULT_SERVER_INI, faraday_server_config)
faraday_user_config = os.path.expanduser("~/.faraday/config/user.xml") faraday_user_config = os.path.expanduser("~/.faraday/config/user.xml")
if not os.path.isfile(faraday_user_config): if not os.path.isfile(faraday_user_config):
shutil.copy(DEFAULT_XML, faraday_user_config) shutil.copy(DEFAULT_XML, faraday_user_config)
if os.path.exists(os.path.expanduser("~/.faraday/config/user.xml")): if os.path.exists(os.path.expanduser("~/.faraday/config/user.xml")):
the_config = Configuration(os.path.expanduser("~/.faraday/config/user.xml")) the_config = Configuration(os.path.expanduser("~/.faraday/config/user.xml"))
else: else:
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<faraday> <faraday>
<appname>Faraday - Penetration Test IDE</appname> <appname>Faraday - Penetration Test IDE</appname>
<version>3.5.0</version> <version>3.6.0</version>
<debug_status>0</debug_status> <debug_status>0</debug_status>
<font>-Misc-Fixed-medium-r-normal-*-12-100-100-100-c-70-iso8859-1</font> <font>-Misc-Fixed-medium-r-normal-*-12-100-100-100-c-70-iso8859-1</font>
<home_path>~/</home_path> <home_path>~/</home_path>
......
...@@ -20,6 +20,7 @@ try: ...@@ -20,6 +20,7 @@ try:
from utils import dependencies from utils import dependencies
from utils.user_input import query_yes_no from utils.user_input import query_yes_no
from faraday import FARADAY_BASE from faraday import FARADAY_BASE
from utils.logs import setUpLogger
from alembic.script import ScriptDirectory from alembic.script import ScriptDirectory
from alembic.config import Config from alembic.config import Config
from alembic.migration import MigrationContext from alembic.migration import MigrationContext
...@@ -139,13 +140,13 @@ def check_alembic_version(): ...@@ -139,13 +140,13 @@ def check_alembic_version():
if head_revision != context.get_current_revision(): if head_revision != context.get_current_revision():
print('--' * 20) print('--' * 20)
print('Missing migrations, please execute: \n\n') print('Missing migrations, please execute: \n\n')
print('python manage.py migrate --upgrade head') print('python manage.py migrate')
sys.exit(1) sys.exit(1)
def main(): def main():
os.chdir(FARADAY_BASE) os.chdir(FARADAY_BASE)
check_postgresql()
check_alembic_version() check_alembic_version()
check_postgresql()
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--ssl', action='store_true', help='enable HTTPS') parser.add_argument('--ssl', action='store_true', help='enable HTTPS')
parser.add_argument('--debug', action='store_true', help='run Faraday Server in debug mode') parser.add_argument('--debug', action='store_true', help='run Faraday Server in debug mode')
...@@ -165,6 +166,7 @@ def main(): ...@@ -165,6 +166,7 @@ def main():
version='Faraday v{version}'.format(version=f_version)) version='Faraday v{version}'.format(version=f_version))
args = parser.parse_args() args = parser.parse_args()
setUpLogger(args.debug)
if args.debug: if args.debug:
server.utils.logger.set_logging_level(server.config.DEBUG) server.utils.logger.set_logging_level(server.config.DEBUG)
......
...@@ -169,7 +169,7 @@ def validate_email(ctx, param, value): ...@@ -169,7 +169,7 @@ def validate_email(ctx, param, value):
@click.option('--email', prompt=True, callback=validate_email) @click.option('--email', prompt=True, callback=validate_email)
@click.option('--password', prompt=True, hide_input=True, @click.option('--password', prompt=True, hide_input=True,
confirmation_prompt=True) confirmation_prompt=True)
def createsuperuser(username, email, password): def create_superuser(username, email, password):
with app.app_context(): with app.app_context():
if db.session.query(User).filter_by(active=True).count() > 0: if db.session.query(User).filter_by(active=True).count() > 0:
print("Can't create more users. The comumunity edition only allows one user. Please contact support for further information.") print("Can't create more users. The comumunity edition only allows one user. Please contact support for further information.")
...@@ -238,7 +238,7 @@ cli.add_command(show_urls) ...@@ -238,7 +238,7 @@ cli.add_command(show_urls)
cli.add_command(initdb) cli.add_command(initdb)
cli.add_command(import_from_couchdb) cli.add_command(import_from_couchdb)
cli.add_command(database_schema) cli.add_command(database_schema)
cli.add_command(createsuperuser) cli.add_command(create_superuser)
cli.add_command(sql_shell) cli.add_command(sql_shell)
cli.add_command(status_check) cli.add_command(status_check)
cli.add_command(create_tables) cli.add_command(create_tables)
......
...@@ -382,7 +382,7 @@ class ReportParser(object): ...@@ -382,7 +382,7 @@ class ReportParser(object):
return "Core Impact" return "Core Impact"
elif tag == "NexposeReport": elif tag == "NexposeReport":
return "NexposeFull" return "NexposeFull"
elif tag == "ASSET_DATA_REPORT" or "SCAN": elif tag in ("ASSET_DATA_REPORT", "SCAN"):
return "Qualysguard" return "Qualysguard"
elif tag == "scanJob": elif tag == "scanJob":
return "Retina" return "Retina"
...@@ -396,5 +396,8 @@ class ReportParser(object): ...@@ -396,5 +396,8 @@ class ReportParser(object):
return "Lynis" return "Lynis"
elif tag == "reconng": elif tag == "reconng":
return "Reconng" return "Reconng"
elif tag == "document":
if re.search("SSLyzeVersion", output) is not None:
return "Sslyze"
else: else:
return None return None
#!/usr/bin/env python3
# Faraday Penetration Test IDE
# Copyright (C) 2013 Infobyte LLC (http://www.infobytesec.com/)
# See the file 'doc/LICENSE' for the license information
'''
Internal script used to detect merge conflicts to branch with
our propiertary code. Not useful if you don't have access to
the code of Faraday Professional or Faraday Corporate
'''
import os
import re
import sys
import subprocess
import logging
import argparse
from contextlib import contextmanager
from tempfile import mkdtemp
from shutil import rmtree
VERSIONS = ['white', 'pink', 'black']
BRANCH_FORMAT = 'origin/{}/dev'
@contextmanager
def chdir(directory):
"""Context manager to work in the specified directory"""
current = os.getcwd()
os.chdir(directory)
yield
os.chdir(current)
@contextmanager
def temp_worktree(branch=None):
"""Context manager that creates a temporal worktree and
changes the current working directory, and when finished
removes the dir and runs a git worktree prune"""
directory = mkdtemp()
cmd = ["git", "worktree", "add", directory]
if branch is not None:
cmd.append(branch)
subprocess.check_output(cmd)
with chdir(directory):
yield
rmtree(directory)
subprocess.check_output(['git', 'worktree', 'prune'])
def check_merge(dst_branch, cur_branch='HEAD'):
"""Return a boolean indicating if the merge from cur_branch
to dst_branch will merge without causing conflicts that need
manual resolution"""
# https://stackoverflow.com/questions/501407/is-there-a-git-merge-dry-run-option
with temp_worktree(dst_branch):
exit_code = subprocess.call(
['git', 'merge', '--no-commit', '--no-ff', cur_branch])
# Use call because it will have exit code 128 when there is nothing to
# abort
subprocess.call(['git', 'merge', '--abort'])
return exit_code == 0
def get_current_branch():
"""Return the current branch of the current workspace"""
# https://stackoverflow.com/questions/6245570/how-to-get-the-current-branch-name-in-git
branch = subprocess.check_output(
['git', 'rev-parse', '--abbrev-ref', 'HEAD']).decode().strip()
if branch == 'HEAD':
# Probably in a detached state inside gitlab CI
# Fallback to the branch name defined in an env var
branch = 'origin/' + os.environ['CI_COMMIT_REF_NAME']
return branch
def branch_exists(branch_name):
exit_code = subprocess.call(
['git', 'rev-parse', '--verify', '--quiet', branch_name])
if exit_code == 0:
return True
elif exit_code == 1:
return False
else:
raise ValueError('Error when checking for branch existence')
def version_of_branch(branch_name):
"""
>>> version_of_branch('tkt_white_this_is_not_a_pink_branch')
'white'
"""
positions = {version: branch_name.find(version)
for version in VERSIONS}
if all((pos < 0) for pos in positions.values()):
# The branch name doesn't contain white, pink or black
return
positions = {version: pos
for (version, pos) in positions.items()
if pos >= 0}
return min(positions.keys(), key=positions.get)
def main(branch):
logging.getLogger().setLevel(getattr(logging, args.log_level.upper()))
logger = logging # TODO FIXME
logger.info('Checking merge conflicts for branch %s', branch)
version = version_of_branch(branch)
if version is None:
logger.error('Unknown version name. Exiting')
sys.exit(-1)
versions_to_test = VERSIONS[VERSIONS.index(version):]
branches_to_test = []
for target_version in versions_to_test:
overriden_branch = branch.replace(version, target_version)
if target_version != version and \
branch_exists(overriden_branch):
branches_to_test.append(overriden_branch)
break # Don't test merge to black if has overriden pink branch
else:
branches_to_test.append(BRANCH_FORMAT.format(target_version))
logging.info('Testing merges in branches %s' % branches_to_test)
success = True
cur_branch = branch
for dst_branch in branches_to_test:
result = check_merge(dst_branch, cur_branch)
if result:
logger.info("Merge into %s succeeded!", dst_branch)
else:
success = False
logger.error("Merge into %s failed :(", dst_branch)
print()
print()
if not success:
sys.exit(1)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('-b', '--branch', default=get_current_branch())
parser.add_argument('-l', '--log-level', default='debug')
args = parser.parse_args()
main(args.branch)
"""empty message
Revision ID: 2ca03a8feef5
Revises: 8a10ff3926a5
Create Date: 2019-01-15 13:02:21.000699+00:00
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '2ca03a8feef5'
down_revision = '8a10ff3926a5'
branch_labels = None
depends_on = None
def upgrade():
op.add_column('workspace', sa.Column('readonly', sa.Boolean(), nullable=False, server_default='False'))
def downgrade():
op.drop_column('workspace', 'readonly')
...@@ -351,7 +351,10 @@ def create_host(workspace_name, host, command_id=None): ...@@ -351,7 +351,10 @@ def create_host(workspace_name, host, command_id=None):