##// END OF EJS Templates
merge: Resolved conflicts
andverb -
r5471:df8a724f merge v5.1.0 stable
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,43 b''
1 |RCE| 5.0.1 |RNS|
2 -----------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2024-05-20
8
9
10 New Features
11 ^^^^^^^^^^^^
12
13
14
15 General
16 ^^^^^^^
17
18
19
20 Security
21 ^^^^^^^^
22
23
24
25 Performance
26 ^^^^^^^^^^^
27
28
29
30
31 Fixes
32 ^^^^^
33
34 - Fixed Celery serialization issues
35 - Fixed Celery startup problems signaling
36 - Fixed SVN hooks binary dir paths which in certain scenarios resulted in empty values forbidding hooks to execute
37 - Fixed annotation bug for files without new lines or mixed newlines
38
39
40 Upgrade notes
41 ^^^^^^^^^^^^^
42
43 - RhodeCode 5.0.1 is unscheduled bugfix release to address some of the issues found during 4.X -> 5.X migration
@@ -0,0 +1,39 b''
1 |RCE| 5.0.2 |RNS|
2 -----------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2024-05-29
8
9
10 New Features
11 ^^^^^^^^^^^^
12
13
14
15 General
16 ^^^^^^^
17
18
19
20 Security
21 ^^^^^^^^
22
23
24
25 Performance
26 ^^^^^^^^^^^
27
28
29
30
31 Fixes
32 ^^^^^
33
34 - Fixed problems with saving branch permissions
35
36 Upgrade notes
37 ^^^^^^^^^^^^^
38
39 - RhodeCode 5.0.2 is unscheduled bugfix release to address some of the issues found during 4.X -> 5.X migration
@@ -0,0 +1,39 b''
1 |RCE| 5.0.3 |RNS|
2 -----------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2024-06-17
8
9
10 New Features
11 ^^^^^^^^^^^^
12
13
14
15 General
16 ^^^^^^^
17
18
19
20 Security
21 ^^^^^^^^
22
23
24
25 Performance
26 ^^^^^^^^^^^
27
28
29
30
31 Fixes
32 ^^^^^
33
34 - Fixed problems nested ldap groups
35
36 Upgrade notes
37 ^^^^^^^^^^^^^
38
39 - RhodeCode 5.0.3 is unscheduled bugfix release to address some of the issues found during 4.X -> 5.X migration
@@ -0,0 +1,59 b''
1 |RCE| 5.1.0 |RNS|
2 -----------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2024-07-18
8
9
10 New Features
11 ^^^^^^^^^^^^
12
13 - We've introduced 2FA for users. Now alongside the external auth 2fa support RhodeCode allows to enable 2FA for users
14 2FA options will be available for each user individually, or enforced via authentication plugins like ldap, or internal.
15 - Email based log-in. RhodeCode now allows to log-in using email as well as username for main authentication type.
16 - Ability to replace a file using web UI. Now one can replace an existing file from the web-ui.
17 - GIT LFS Sync automation. Remote push/pull commands now can also sync GIT LFS objects.
18 - Added ability to remove or close branches from the web ui
19 - Added ability to delete a branch automatically after merging PR for git repositories
20 - Added support for S3 based archive_cache based that allows storing cached archives in S3 compatible object store.
21
22
23 General
24 ^^^^^^^
25
26 - Upgraded all dependency libraries to their latest available versions
27 - Repository storage is no longer controlled via DB settings, but .ini file. This allows easier automated deployments.
28 - Bumped mercurial to 6.7.4
29 - Mercurial: enable httppostarguments for better support of large repositories with lots of heads.
30 - Added explicit db-migrate step to update hooks for 5.X release.
31
32
33 Security
34 ^^^^^^^^
35
36
37
38 Performance
39 ^^^^^^^^^^^
40
41 - Introduced a full rewrite of ssh backend for performance. The result is 2-5x speed improvement for operation with ssh.
42 enable new ssh wrapper by setting: `ssh.wrapper_cmd = /home/rhodecode/venv/bin/rc-ssh-wrapper-v2`
43 - Introduced a new hooks subsystem that is more scalable and faster, enable it by settings: `vcs.hooks.protocol = celery`
44
45
46 Fixes
47 ^^^^^
48
49 - Archives: Zip archive download breaks when a gitmodules file is present
50 - Branch permissions: fixed bug preventing to specify own rules from 4.X install
51 - SVN: refactored svn events, thus fixing support for it in dockerized env
52 - Fixed empty server url in PR link after push from cli
53
54
55 Upgrade notes
56 ^^^^^^^^^^^^^
57
58 - RhodeCode 5.1.0 is a mayor feature release after big 5.0.0 python3 migration. Happy to ship a first time feature
59 rich release
@@ -0,0 +1,55 b''
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
20 import pytest
21
22 from rhodecode.api.tests.utils import (
23 build_data, api_call)
24
25
26 @pytest.mark.usefixtures("app")
27 class TestServiceApi:
28
29 def test_service_api_with_wrong_secret(self):
30 id, payload = build_data("wrong_api_key", 'service_get_repo_name_by_id')
31 response = api_call(self.app, payload)
32
33 assert 'Invalid API KEY' == response.json['error']
34
35 def test_service_api_with_legit_secret(self):
36 id, payload = build_data(self.app.app.config.get_settings()['app.service_api.token'],
37 'service_get_repo_name_by_id', repo_id='1')
38 response = api_call(self.app, payload)
39 assert not response.json['error']
40
41 def test_service_api_not_a_part_of_public_api_suggestions(self):
42 id, payload = build_data("secret", 'some_random_guess_method')
43 response = api_call(self.app, payload)
44 assert 'service_' not in response.json['error']
45
46 def test_service_get_data_for_ssh_wrapper_output(self):
47 id, payload = build_data(
48 self.app.app.config.get_settings()['app.service_api.token'],
49 'service_get_data_for_ssh_wrapper',
50 user_id=1,
51 repo_name='vcs_test_git')
52 response = api_call(self.app, payload)
53
54 assert ['branch_permissions', 'repo_permissions', 'repos_path', 'user_id', 'username']\
55 == list(response.json['result'].keys())
@@ -0,0 +1,125 b''
1 # Copyright (C) 2011-2023 RhodeCode GmbH
2 #
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19 import logging
20 import datetime
21 from collections import defaultdict
22
23 from sqlalchemy import Table
24 from rhodecode.api import jsonrpc_method, SERVICE_API_IDENTIFIER
25
26
27 log = logging.getLogger(__name__)
28
29
30 @jsonrpc_method()
31 def service_get_data_for_ssh_wrapper(request, apiuser, user_id, repo_name, key_id=None):
32 from rhodecode.model.db import User
33 from rhodecode.model.scm import ScmModel
34 from rhodecode.model.meta import raw_query_executor, Base
35
36 if key_id:
37 table = Table('user_ssh_keys', Base.metadata, autoload=False)
38 atime = datetime.datetime.utcnow()
39 stmt = (
40 table.update()
41 .where(table.c.ssh_key_id == key_id)
42 .values(accessed_on=atime)
43 )
44
45 res_count = None
46 with raw_query_executor() as session:
47 result = session.execute(stmt)
48 if result.rowcount:
49 res_count = result.rowcount
50
51 if res_count:
52 log.debug(f'Update key id:{key_id} access time')
53 db_user = User.get(user_id)
54 if not db_user:
55 return None
56 auth_user = db_user.AuthUser()
57
58 return {
59 'user_id': db_user.user_id,
60 'username': db_user.username,
61 'repo_permissions': auth_user.permissions['repositories'],
62 "branch_permissions": auth_user.get_branch_permissions(repo_name),
63 "repos_path": ScmModel().repos_path
64 }
65
66
67 @jsonrpc_method()
68 def service_get_repo_name_by_id(request, apiuser, repo_id):
69 from rhodecode.model.repo import RepoModel
70 by_id_match = RepoModel().get_repo_by_id(repo_id)
71 if by_id_match:
72 repo_name = by_id_match.repo_name
73 return {
74 'repo_name': repo_name
75 }
76 return None
77
78
79 @jsonrpc_method()
80 def service_mark_for_invalidation(request, apiuser, repo_name):
81 from rhodecode.model.scm import ScmModel
82 ScmModel().mark_for_invalidation(repo_name)
83 return {'msg': "Applied"}
84
85
86 @jsonrpc_method()
87 def service_config_to_hgrc(request, apiuser, cli_flags, repo_name):
88 from rhodecode.model.db import RhodeCodeUi
89 from rhodecode.model.settings import VcsSettingsModel
90
91 ui_sections = defaultdict(list)
92 ui = VcsSettingsModel(repo=repo_name).get_ui_settings(section=None, key=None)
93
94 default_hooks = [
95 ('pretxnchangegroup.ssh_auth', 'python:vcsserver.hooks.pre_push_ssh_auth'),
96 ('pretxnchangegroup.ssh', 'python:vcsserver.hooks.pre_push_ssh'),
97 ('changegroup.ssh', 'python:vcsserver.hooks.post_push_ssh'),
98
99 ('preoutgoing.ssh', 'python:vcsserver.hooks.pre_pull_ssh'),
100 ('outgoing.ssh', 'python:vcsserver.hooks.post_pull_ssh'),
101 ]
102
103 for k, v in default_hooks:
104 ui_sections['hooks'].append((k, v))
105
106 for entry in ui:
107 if not entry.active:
108 continue
109 sec = entry.section
110 key = entry.key
111
112 if sec in cli_flags:
113 # we want only custom hooks, so we skip builtins
114 if sec == 'hooks' and key in RhodeCodeUi.HOOKS_BUILTIN:
115 continue
116
117 ui_sections[sec].append([key, entry.value])
118
119 flags = []
120 for _sec, key_val in ui_sections.items():
121 flags.append(' ')
122 flags.append(f'[{_sec}]')
123 for key, val in key_val:
124 flags.append(f'{key}= {val}')
125 return {'flags': flags}
@@ -0,0 +1,67 b''
1 import pytest
2 import mock
3
4 from rhodecode.lib.type_utils import AttributeDict
5 from rhodecode.model.meta import Session
6 from rhodecode.tests.fixture import Fixture
7 from rhodecode.tests.routes import route_path
8 from rhodecode.model.settings import SettingsModel
9
10 fixture = Fixture()
11
12
13 @pytest.mark.usefixtures('app')
14 class Test2FA(object):
15 @classmethod
16 def setup_class(cls):
17 cls.password = 'valid-one'
18
19 def test_redirect_to_2fa_setup_if_enabled_for_user(self, user_util):
20 user = user_util.create_user(password=self.password)
21 user.has_enabled_2fa = True
22 self.app.post(
23 route_path('login'),
24 {'username': user.username,
25 'password': self.password})
26
27 response = self.app.get('/')
28 assert response.status_code == 302
29 assert response.location.endswith(route_path('setup_2fa'))
30
31 def test_redirect_to_2fa_check_if_2fa_configured(self, user_util):
32 user = user_util.create_user(password=self.password)
33 user.has_enabled_2fa = True
34 user.init_secret_2fa()
35 Session().add(user)
36 Session().commit()
37 self.app.post(
38 route_path('login'),
39 {'username': user.username,
40 'password': self.password})
41 response = self.app.get('/')
42 assert response.status_code == 302
43 assert response.location.endswith(route_path('check_2fa'))
44
45 def test_2fa_recovery_codes_works_only_once(self, user_util):
46 user = user_util.create_user(password=self.password)
47 user.has_enabled_2fa = True
48 user.init_secret_2fa()
49 recovery_code_to_check = user.init_2fa_recovery_codes()[0]
50 Session().add(user)
51 Session().commit()
52 self.app.post(
53 route_path('login'),
54 {'username': user.username,
55 'password': self.password})
56 response = self.app.post(route_path('check_2fa'), {'totp': recovery_code_to_check})
57 assert response.status_code == 302
58 response = self.app.post(route_path('check_2fa'), {'totp': recovery_code_to_check})
59 response.mustcontain('Code is invalid. Try again!')
60
61 def test_2fa_state_when_forced_by_admin(self, user_util):
62 user = user_util.create_user(password=self.password)
63 user.has_enabled_2fa = False
64 with mock.patch.object(
65 SettingsModel, 'get_setting_by_name', lambda *a, **kw: AttributeDict(app_settings_value=True)):
66
67 assert user.has_enabled_2fa
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,5 +1,5 b''
1 [bumpversion]
1 [bumpversion]
2 current_version = 5.0.3
2 current_version = 5.1.0
3 message = release: Bump version {current_version} to {new_version}
3 message = release: Bump version {current_version} to {new_version}
4
4
5 [bumpversion:file:rhodecode/VERSION]
5 [bumpversion:file:rhodecode/VERSION]
@@ -1,192 +1,171 b''
1 # required for pushd to work..
1 # required for pushd to work..
2 SHELL = /bin/bash
2 SHELL = /bin/bash
3
3
4
4
5 # set by: PATH_TO_OUTDATED_PACKAGES=/some/path/outdated_packages.py
5 # set by: PATH_TO_OUTDATED_PACKAGES=/some/path/outdated_packages.py
6 OUTDATED_PACKAGES = ${PATH_TO_OUTDATED_PACKAGES}
6 OUTDATED_PACKAGES = ${PATH_TO_OUTDATED_PACKAGES}
7
7
8 .PHONY: clean
8 .PHONY: clean
9 ## Cleanup compiled and cache py files
9 ## Cleanup compiled and cache py files
10 clean:
10 clean:
11 make test-clean
11 make test-clean
12 find . -type f \( -iname '*.c' -o -iname '*.pyc' -o -iname '*.so' -o -iname '*.orig' \) -exec rm '{}' ';'
12 find . -type f \( -iname '*.c' -o -iname '*.pyc' -o -iname '*.so' -o -iname '*.orig' \) -exec rm '{}' ';'
13 find . -type d -name "build" -prune -exec rm -rf '{}' ';'
13 find . -type d -name "build" -prune -exec rm -rf '{}' ';'
14
14
15
15
16 .PHONY: test
16 .PHONY: test
17 ## run test-clean and tests
17 ## run test-clean and tests
18 test:
18 test:
19 make test-clean
19 make test-clean
20 make test-only
20 make test-only
21
21
22
22
23 .PHONY: test-clean
23 .PHONY: test-clean
24 ## run test-clean and tests
24 ## run test-clean and tests
25 test-clean:
25 test-clean:
26 rm -rf coverage.xml htmlcov junit.xml pylint.log result
26 rm -rf coverage.xml htmlcov junit.xml pylint.log result
27 find . -type d -name "__pycache__" -prune -exec rm -rf '{}' ';'
27 find . -type d -name "__pycache__" -prune -exec rm -rf '{}' ';'
28 find . -type f \( -iname '.coverage.*' \) -exec rm '{}' ';'
28 find . -type f \( -iname '.coverage.*' \) -exec rm '{}' ';'
29
29
30
30
31 .PHONY: test-only
31 .PHONY: test-only
32 ## Run tests only without cleanup
32 ## Run tests only without cleanup
33 test-only:
33 test-only:
34 PYTHONHASHSEED=random \
34 PYTHONHASHSEED=random \
35 py.test -x -vv -r xw -p no:sugar \
35 py.test -x -vv -r xw -p no:sugar \
36 --cov-report=term-missing --cov-report=html \
36 --cov-report=term-missing --cov-report=html \
37 --cov=rhodecode rhodecode
37 --cov=rhodecode rhodecode
38
38
39
39
40 .PHONY: test-only-mysql
41 ## run tests against mysql
42 test-only-mysql:
43 PYTHONHASHSEED=random \
44 py.test -x -vv -r xw -p no:sugar \
45 --cov-report=term-missing --cov-report=html \
46 --ini-config-override='{"app:main": {"sqlalchemy.db1.url": "mysql://root:qweqwe@localhost/rhodecode_test?charset=utf8"}}' \
47 --cov=rhodecode rhodecode
48
49
50 .PHONY: test-only-postgres
51 ## run tests against postgres
52 test-only-postgres:
53 PYTHONHASHSEED=random \
54 py.test -x -vv -r xw -p no:sugar \
55 --cov-report=term-missing --cov-report=html \
56 --ini-config-override='{"app:main": {"sqlalchemy.db1.url": "postgresql://postgres:qweqwe@localhost/rhodecode_test"}}' \
57 --cov=rhodecode rhodecode
58
59 .PHONY: ruff-check
60 ## run a ruff analysis
61 ruff-check:
62 ruff check --ignore F401 --ignore I001 --ignore E402 --ignore E501 --ignore F841 --exclude rhodecode/lib/dbmigrate --exclude .eggs --exclude .dev .
63
64
65 .PHONY: docs
40 .PHONY: docs
66 ## build docs
41 ## build docs
67 docs:
42 docs:
68 (cd docs; docker run --rm -v $(PWD):/project --workdir=/project/docs sphinx-doc-build-rc make clean html SPHINXOPTS="-W")
43 (cd docs; docker run --rm -v $(PWD):/project --workdir=/project/docs sphinx-doc-build-rc make clean html SPHINXOPTS="-W")
69
44
70
45
71 .PHONY: docs-clean
46 .PHONY: docs-clean
72 ## Cleanup docs
47 ## Cleanup docs
73 docs-clean:
48 docs-clean:
74 (cd docs; docker run --rm -v $(PWD):/project --workdir=/project/docs sphinx-doc-build-rc make clean)
49 (cd docs; docker run --rm -v $(PWD):/project --workdir=/project/docs sphinx-doc-build-rc make clean)
75
50
76
51
77 .PHONY: docs-cleanup
52 .PHONY: docs-cleanup
78 ## Cleanup docs
53 ## Cleanup docs
79 docs-cleanup:
54 docs-cleanup:
80 (cd docs; docker run --rm -v $(PWD):/project --workdir=/project/docs sphinx-doc-build-rc make cleanup)
55 (cd docs; docker run --rm -v $(PWD):/project --workdir=/project/docs sphinx-doc-build-rc make cleanup)
81
56
82
57
83 .PHONY: web-build
58 .PHONY: web-build
84 ## Build JS packages static/js
59 ## Build JS packages static/js
85 web-build:
60 web-build:
86 docker run -it --rm -v $(PWD):/project --workdir=/project rhodecode/static-files-build:16 -c "npm install && /project/node_modules/.bin/grunt"
61 docker run -it --rm -v $(PWD):/project --workdir=/project rhodecode/static-files-build:16 -c "npm install && /project/node_modules/.bin/grunt"
87 # run static file check
62 # run static file check
88 ./rhodecode/tests/scripts/static-file-check.sh rhodecode/public/
63 ./rhodecode/tests/scripts/static-file-check.sh rhodecode/public/
89 rm -rf node_modules
64 rm -rf node_modules
90
65
66 .PHONY: ruff-check
67 ## run a ruff analysis
68 ruff-check:
69 ruff check --ignore F401 --ignore I001 --ignore E402 --ignore E501 --ignore F841 --exclude rhodecode/lib/dbmigrate --exclude .eggs --exclude .dev .
91
70
92 .PHONY: pip-packages
71 .PHONY: pip-packages
93 ## Show outdated packages
72 ## Show outdated packages
94 pip-packages:
73 pip-packages:
95 python ${OUTDATED_PACKAGES}
74 python ${OUTDATED_PACKAGES}
96
75
97
76
98 .PHONY: build
77 .PHONY: build
99 ## Build sdist/egg
78 ## Build sdist/egg
100 build:
79 build:
101 python -m build
80 python -m build
102
81
103
82
104 .PHONY: dev-sh
83 .PHONY: dev-sh
105 ## make dev-sh
84 ## make dev-sh
106 dev-sh:
85 dev-sh:
107 sudo echo "deb [trusted=yes] https://apt.fury.io/rsteube/ /" | sudo tee -a "/etc/apt/sources.list.d/fury.list"
86 sudo echo "deb [trusted=yes] https://apt.fury.io/rsteube/ /" | sudo tee -a "/etc/apt/sources.list.d/fury.list"
108 sudo apt-get update
87 sudo apt-get update
109 sudo apt-get install -y zsh carapace-bin
88 sudo apt-get install -y zsh carapace-bin
110 rm -rf /home/rhodecode/.oh-my-zsh
89 rm -rf /home/rhodecode/.oh-my-zsh
111 curl https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh | sh
90 curl https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh | sh
112 echo "source <(carapace _carapace)" > /home/rhodecode/.zsrc
91 @echo "source <(carapace _carapace)" > /home/rhodecode/.zsrc
113 PROMPT='%(?.%F{green}√.%F{red}?%?)%f %B%F{240}%1~%f%b %# ' zsh
92 @echo "${RC_DEV_CMD_HELP}"
93 @PROMPT='%(?.%F{green}√.%F{red}?%?)%f %B%F{240}%1~%f%b %# ' zsh
114
94
115
95
116 .PHONY: dev-cleanup
96 .PHONY: dev-cleanup
117 ## Cleanup: pip freeze | grep -v "^-e" | grep -v "@" | xargs pip uninstall -y
97 ## Cleanup: pip freeze | grep -v "^-e" | grep -v "@" | xargs pip uninstall -y
118 dev-cleanup:
98 dev-cleanup:
119 pip freeze | grep -v "^-e" | grep -v "@" | xargs pip uninstall -y
99 pip freeze | grep -v "^-e" | grep -v "@" | xargs pip uninstall -y
120 rm -rf /tmp/*
100 rm -rf /tmp/*
121
101
122
102
123 .PHONY: dev-env
103 .PHONY: dev-env
124 ## make dev-env based on the requirements files and install develop of packages
104 ## make dev-env based on the requirements files and install develop of packages
105 ## Cleanup: pip freeze | grep -v "^-e" | grep -v "@" | xargs pip uninstall -y
125 dev-env:
106 dev-env:
107 sudo -u root chown rhodecode:rhodecode /home/rhodecode/.cache/pip/
126 pip install build virtualenv
108 pip install build virtualenv
127 pushd ../rhodecode-vcsserver/ && make dev-env && popd
109 pushd ../rhodecode-vcsserver/ && make dev-env && popd
128 pip wheel --wheel-dir=/home/rhodecode/.cache/pip/wheels -r requirements.txt -r requirements_rc_tools.txt -r requirements_test.txt -r requirements_debug.txt
110 pip wheel --wheel-dir=/home/rhodecode/.cache/pip/wheels -r requirements.txt -r requirements_rc_tools.txt -r requirements_test.txt -r requirements_debug.txt
129 pip install --no-index --find-links=/home/rhodecode/.cache/pip/wheels -r requirements.txt -r requirements_rc_tools.txt -r requirements_test.txt -r requirements_debug.txt
111 pip install --no-index --find-links=/home/rhodecode/.cache/pip/wheels -r requirements.txt -r requirements_rc_tools.txt -r requirements_test.txt -r requirements_debug.txt
130 pip install -e .
112 pip install -e .
131
113
132
114
133 .PHONY: sh
115 .PHONY: sh
134 ## shortcut for make dev-sh dev-env
116 ## shortcut for make dev-sh dev-env
135 sh:
117 sh:
136 make dev-env
118 make dev-env
137 make dev-sh
119 make dev-sh
138
120
139
121
140 .PHONY: dev-srv
122 ## Allows changes of workers e.g make dev-srv-g workers=2
141 ## run develop server instance, docker exec -it $(docker ps -q --filter 'name=dev-enterprise-ce') /bin/bash
123 workers?=1
142 dev-srv:
143 pserve --reload .dev/dev.ini
144
124
145
125 .PHONY: dev-srv
146 .PHONY: dev-srv-g
126 ## run gunicorn web server with reloader, use workers=N to set multiworker mode
147 ## run gunicorn multi process workers
127 dev-srv:
148 dev-srv-g:
128 gunicorn --paste=.dev/dev.ini --bind=0.0.0.0:10020 --config=.dev/gunicorn_config.py --timeout=120 --reload --workers=$(workers)
149 gunicorn --paste .dev/dev.ini --bind=0.0.0.0:10020 --config=.dev/gunicorn_config.py --timeout=120 --reload
150
129
151
130
152 # Default command on calling make
131 # Default command on calling make
153 .DEFAULT_GOAL := show-help
132 .DEFAULT_GOAL := show-help
154
133
155 .PHONY: show-help
134 .PHONY: show-help
156 show-help:
135 show-help:
157 @echo "$$(tput bold)Available rules:$$(tput sgr0)"
136 @echo "$$(tput bold)Available rules:$$(tput sgr0)"
158 @echo
137 @echo
159 @sed -n -e "/^## / { \
138 @sed -n -e "/^## / { \
160 h; \
139 h; \
161 s/.*//; \
140 s/.*//; \
162 :doc" \
141 :doc" \
163 -e "H; \
142 -e "H; \
164 n; \
143 n; \
165 s/^## //; \
144 s/^## //; \
166 t doc" \
145 t doc" \
167 -e "s/:.*//; \
146 -e "s/:.*//; \
168 G; \
147 G; \
169 s/\\n## /---/; \
148 s/\\n## /---/; \
170 s/\\n/ /g; \
149 s/\\n/ /g; \
171 p; \
150 p; \
172 }" ${MAKEFILE_LIST} \
151 }" ${MAKEFILE_LIST} \
173 | LC_ALL='C' sort --ignore-case \
152 | LC_ALL='C' sort --ignore-case \
174 | awk -F '---' \
153 | awk -F '---' \
175 -v ncol=$$(tput cols) \
154 -v ncol=$$(tput cols) \
176 -v indent=19 \
155 -v indent=19 \
177 -v col_on="$$(tput setaf 6)" \
156 -v col_on="$$(tput setaf 6)" \
178 -v col_off="$$(tput sgr0)" \
157 -v col_off="$$(tput sgr0)" \
179 '{ \
158 '{ \
180 printf "%s%*s%s ", col_on, -indent, $$1, col_off; \
159 printf "%s%*s%s ", col_on, -indent, $$1, col_off; \
181 n = split($$2, words, " "); \
160 n = split($$2, words, " "); \
182 line_length = ncol - indent; \
161 line_length = ncol - indent; \
183 for (i = 1; i <= n; i++) { \
162 for (i = 1; i <= n; i++) { \
184 line_length -= length(words[i]) + 1; \
163 line_length -= length(words[i]) + 1; \
185 if (line_length <= 0) { \
164 if (line_length <= 0) { \
186 line_length = ncol - indent - length(words[i]) - 1; \
165 line_length = ncol - indent - length(words[i]) - 1; \
187 printf "\n%*s ", -indent, " "; \
166 printf "\n%*s ", -indent, " "; \
188 } \
167 } \
189 printf "%s ", words[i]; \
168 printf "%s ", words[i]; \
190 } \
169 } \
191 printf "\n"; \
170 printf "\n"; \
192 }'
171 }'
@@ -1,865 +1,856 b''
1
1
2 ; #########################################
2 ; #########################################
3 ; RHODECODE COMMUNITY EDITION CONFIGURATION
3 ; RHODECODE COMMUNITY EDITION CONFIGURATION
4 ; #########################################
4 ; #########################################
5
5
6 [DEFAULT]
6 [DEFAULT]
7 ; Debug flag sets all loggers to debug, and enables request tracking
7 ; Debug flag sets all loggers to debug, and enables request tracking
8 debug = true
8 debug = true
9
9
10 ; ########################################################################
10 ; ########################################################################
11 ; EMAIL CONFIGURATION
11 ; EMAIL CONFIGURATION
12 ; These settings will be used by the RhodeCode mailing system
12 ; These settings will be used by the RhodeCode mailing system
13 ; ########################################################################
13 ; ########################################################################
14
14
15 ; prefix all emails subjects with given prefix, helps filtering out emails
15 ; prefix all emails subjects with given prefix, helps filtering out emails
16 #email_prefix = [RhodeCode]
16 #email_prefix = [RhodeCode]
17
17
18 ; email FROM address all mails will be sent
18 ; email FROM address all mails will be sent
19 #app_email_from = rhodecode-noreply@localhost
19 #app_email_from = rhodecode-noreply@localhost
20
20
21 #smtp_server = mail.server.com
21 #smtp_server = mail.server.com
22 #smtp_username =
22 #smtp_username =
23 #smtp_password =
23 #smtp_password =
24 #smtp_port =
24 #smtp_port =
25 #smtp_use_tls = false
25 #smtp_use_tls = false
26 #smtp_use_ssl = true
26 #smtp_use_ssl = true
27
27
28 [server:main]
28 [server:main]
29 ; COMMON HOST/IP CONFIG, This applies mostly to develop setup,
29 ; COMMON HOST/IP CONFIG, This applies mostly to develop setup,
30 ; Host port for gunicorn are controlled by gunicorn_conf.py
30 ; Host port for gunicorn are controlled by gunicorn_conf.py
31 host = 127.0.0.1
31 host = 127.0.0.1
32 port = 10020
32 port = 10020
33
33
34 ; ##################################################
35 ; WAITRESS WSGI SERVER - Recommended for Development
36 ; ##################################################
37
38 ; use server type
39 use = egg:waitress#main
40
41 ; number of worker threads
42 threads = 5
43
44 ; MAX BODY SIZE 100GB
45 max_request_body_size = 107374182400
46
47 ; Use poll instead of select, fixes file descriptors limits problems.
48 ; May not work on old windows systems.
49 asyncore_use_poll = true
50
51
34
52 ; ###########################
35 ; ###########################
53 ; GUNICORN APPLICATION SERVER
36 ; GUNICORN APPLICATION SERVER
54 ; ###########################
37 ; ###########################
55
38
56 ; run with gunicorn --paste rhodecode.ini --config gunicorn_conf.py
39 ; run with gunicorn --config gunicorn_conf.py --paste rhodecode.ini
57
40
58 ; Module to use, this setting shouldn't be changed
41 ; Module to use, this setting shouldn't be changed
59 #use = egg:gunicorn#main
42 use = egg:gunicorn#main
60
43
61 ; Prefix middleware for RhodeCode.
44 ; Prefix middleware for RhodeCode.
62 ; recommended when using proxy setup.
45 ; recommended when using proxy setup.
63 ; allows to set RhodeCode under a prefix in server.
46 ; allows to set RhodeCode under a prefix in server.
64 ; eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
47 ; eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
65 ; And set your prefix like: `prefix = /custom_prefix`
48 ; And set your prefix like: `prefix = /custom_prefix`
66 ; be sure to also set beaker.session.cookie_path = /custom_prefix if you need
49 ; be sure to also set beaker.session.cookie_path = /custom_prefix if you need
67 ; to make your cookies only work on prefix url
50 ; to make your cookies only work on prefix url
68 [filter:proxy-prefix]
51 [filter:proxy-prefix]
69 use = egg:PasteDeploy#prefix
52 use = egg:PasteDeploy#prefix
70 prefix = /
53 prefix = /
71
54
72 [app:main]
55 [app:main]
73 ; The %(here)s variable will be replaced with the absolute path of parent directory
56 ; The %(here)s variable will be replaced with the absolute path of parent directory
74 ; of this file
57 ; of this file
75 ; Each option in the app:main can be override by an environmental variable
58 ; Each option in the app:main can be override by an environmental variable
76 ;
59 ;
77 ;To override an option:
60 ;To override an option:
78 ;
61 ;
79 ;RC_<KeyName>
62 ;RC_<KeyName>
80 ;Everything should be uppercase, . and - should be replaced by _.
63 ;Everything should be uppercase, . and - should be replaced by _.
81 ;For example, if you have these configuration settings:
64 ;For example, if you have these configuration settings:
82 ;rc_cache.repo_object.backend = foo
65 ;rc_cache.repo_object.backend = foo
83 ;can be overridden by
66 ;can be overridden by
84 ;export RC_CACHE_REPO_OBJECT_BACKEND=foo
67 ;export RC_CACHE_REPO_OBJECT_BACKEND=foo
85
68
86 use = egg:rhodecode-enterprise-ce
69 use = egg:rhodecode-enterprise-ce
87
70
88 ; enable proxy prefix middleware, defined above
71 ; enable proxy prefix middleware, defined above
89 #filter-with = proxy-prefix
72 #filter-with = proxy-prefix
90
73
91 ; #############
74 ; #############
92 ; DEBUG OPTIONS
75 ; DEBUG OPTIONS
93 ; #############
76 ; #############
94
77
95 pyramid.reload_templates = true
78 pyramid.reload_templates = true
96
79
97 # During development the we want to have the debug toolbar enabled
80 # During development the we want to have the debug toolbar enabled
98 pyramid.includes =
81 pyramid.includes =
99 pyramid_debugtoolbar
82 pyramid_debugtoolbar
100
83
101 debugtoolbar.hosts = 0.0.0.0/0
84 debugtoolbar.hosts = 0.0.0.0/0
102 debugtoolbar.exclude_prefixes =
85 debugtoolbar.exclude_prefixes =
103 /css
86 /css
104 /fonts
87 /fonts
105 /images
88 /images
106 /js
89 /js
107
90
108 ## RHODECODE PLUGINS ##
91 ## RHODECODE PLUGINS ##
109 rhodecode.includes =
92 rhodecode.includes =
110 rhodecode.api
93 rhodecode.api
111
94
112
95
113 # api prefix url
96 # api prefix url
114 rhodecode.api.url = /_admin/api
97 rhodecode.api.url = /_admin/api
115
98
116 ; enable debug style page
99 ; enable debug style page
117 debug_style = true
100 debug_style = true
118
101
119 ; #################
102 ; #################
120 ; END DEBUG OPTIONS
103 ; END DEBUG OPTIONS
121 ; #################
104 ; #################
122
105
123 ; encryption key used to encrypt social plugin tokens,
106 ; encryption key used to encrypt social plugin tokens,
124 ; remote_urls with credentials etc, if not set it defaults to
107 ; remote_urls with credentials etc, if not set it defaults to
125 ; `beaker.session.secret`
108 ; `beaker.session.secret`
126 #rhodecode.encrypted_values.secret =
109 #rhodecode.encrypted_values.secret =
127
110
128 ; decryption strict mode (enabled by default). It controls if decryption raises
111 ; decryption strict mode (enabled by default). It controls if decryption raises
129 ; `SignatureVerificationError` in case of wrong key, or damaged encryption data.
112 ; `SignatureVerificationError` in case of wrong key, or damaged encryption data.
130 #rhodecode.encrypted_values.strict = false
113 #rhodecode.encrypted_values.strict = false
131
114
132 ; Pick algorithm for encryption. Either fernet (more secure) or aes (default)
115 ; Pick algorithm for encryption. Either fernet (more secure) or aes (default)
133 ; fernet is safer, and we strongly recommend switching to it.
116 ; fernet is safer, and we strongly recommend switching to it.
134 ; Due to backward compatibility aes is used as default.
117 ; Due to backward compatibility aes is used as default.
135 #rhodecode.encrypted_values.algorithm = fernet
118 #rhodecode.encrypted_values.algorithm = fernet
136
119
137 ; Return gzipped responses from RhodeCode (static files/application)
120 ; Return gzipped responses from RhodeCode (static files/application)
138 gzip_responses = false
121 gzip_responses = false
139
122
140 ; Auto-generate javascript routes file on startup
123 ; Auto-generate javascript routes file on startup
141 generate_js_files = false
124 generate_js_files = false
142
125
143 ; System global default language.
126 ; System global default language.
144 ; All available languages: en (default), be, de, es, fr, it, ja, pl, pt, ru, zh
127 ; All available languages: en (default), be, de, es, fr, it, ja, pl, pt, ru, zh
145 lang = en
128 lang = en
146
129
147 ; Perform a full repository scan and import on each server start.
130 ; Perform a full repository scan and import on each server start.
148 ; Settings this to true could lead to very long startup time.
131 ; Settings this to true could lead to very long startup time.
149 startup.import_repos = false
132 startup.import_repos = false
150
133
151 ; URL at which the application is running. This is used for Bootstrapping
134 ; URL at which the application is running. This is used for Bootstrapping
152 ; requests in context when no web request is available. Used in ishell, or
135 ; requests in context when no web request is available. Used in ishell, or
153 ; SSH calls. Set this for events to receive proper url for SSH calls.
136 ; SSH calls. Set this for events to receive proper url for SSH calls.
154 app.base_url = http://rhodecode.local
137 app.base_url = http://rhodecode.local
155
138
139 ; Host at which the Service API is running.
140 app.service_api.host = http://rhodecode.local:10020
141
142 ; Secret for Service API authentication.
143 app.service_api.token =
144
156 ; Unique application ID. Should be a random unique string for security.
145 ; Unique application ID. Should be a random unique string for security.
157 app_instance_uuid = rc-production
146 app_instance_uuid = rc-production
158
147
159 ; Cut off limit for large diffs (size in bytes). If overall diff size on
148 ; Cut off limit for large diffs (size in bytes). If overall diff size on
160 ; commit, or pull request exceeds this limit this diff will be displayed
149 ; commit, or pull request exceeds this limit this diff will be displayed
161 ; partially. E.g 512000 == 512Kb
150 ; partially. E.g 512000 == 512Kb
162 cut_off_limit_diff = 512000
151 cut_off_limit_diff = 512000
163
152
164 ; Cut off limit for large files inside diffs (size in bytes). Each individual
153 ; Cut off limit for large files inside diffs (size in bytes). Each individual
165 ; file inside diff which exceeds this limit will be displayed partially.
154 ; file inside diff which exceeds this limit will be displayed partially.
166 ; E.g 128000 == 128Kb
155 ; E.g 128000 == 128Kb
167 cut_off_limit_file = 128000
156 cut_off_limit_file = 128000
168
157
169 ; Use cached version of vcs repositories everywhere. Recommended to be `true`
158 ; Use cached version of vcs repositories everywhere. Recommended to be `true`
170 vcs_full_cache = true
159 vcs_full_cache = true
171
160
172 ; Force https in RhodeCode, fixes https redirects, assumes it's always https.
161 ; Force https in RhodeCode, fixes https redirects, assumes it's always https.
173 ; Normally this is controlled by proper flags sent from http server such as Nginx or Apache
162 ; Normally this is controlled by proper flags sent from http server such as Nginx or Apache
174 force_https = false
163 force_https = false
175
164
176 ; use Strict-Transport-Security headers
165 ; use Strict-Transport-Security headers
177 use_htsts = false
166 use_htsts = false
178
167
179 ; Set to true if your repos are exposed using the dumb protocol
168 ; Set to true if your repos are exposed using the dumb protocol
180 git_update_server_info = false
169 git_update_server_info = false
181
170
182 ; RSS/ATOM feed options
171 ; RSS/ATOM feed options
183 rss_cut_off_limit = 256000
172 rss_cut_off_limit = 256000
184 rss_items_per_page = 10
173 rss_items_per_page = 10
185 rss_include_diff = false
174 rss_include_diff = false
186
175
187 ; gist URL alias, used to create nicer urls for gist. This should be an
176 ; gist URL alias, used to create nicer urls for gist. This should be an
188 ; url that does rewrites to _admin/gists/{gistid}.
177 ; url that does rewrites to _admin/gists/{gistid}.
189 ; example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
178 ; example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
190 ; RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
179 ; RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
191 gist_alias_url =
180 gist_alias_url =
192
181
193 ; List of views (using glob pattern syntax) that AUTH TOKENS could be
182 ; List of views (using glob pattern syntax) that AUTH TOKENS could be
194 ; used for access.
183 ; used for access.
195 ; Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
184 ; Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
196 ; came from the the logged in user who own this authentication token.
185 ; came from the the logged in user who own this authentication token.
197 ; Additionally @TOKEN syntax can be used to bound the view to specific
186 ; Additionally @TOKEN syntax can be used to bound the view to specific
198 ; authentication token. Such view would be only accessible when used together
187 ; authentication token. Such view would be only accessible when used together
199 ; with this authentication token
188 ; with this authentication token
200 ; list of all views can be found under `/_admin/permissions/auth_token_access`
189 ; list of all views can be found under `/_admin/permissions/auth_token_access`
201 ; The list should be "," separated and on a single line.
190 ; The list should be "," separated and on a single line.
202 ; Most common views to enable:
191 ; Most common views to enable:
203
192
204 # RepoCommitsView:repo_commit_download
193 # RepoCommitsView:repo_commit_download
205 # RepoCommitsView:repo_commit_patch
194 # RepoCommitsView:repo_commit_patch
206 # RepoCommitsView:repo_commit_raw
195 # RepoCommitsView:repo_commit_raw
207 # RepoCommitsView:repo_commit_raw@TOKEN
196 # RepoCommitsView:repo_commit_raw@TOKEN
208 # RepoFilesView:repo_files_diff
197 # RepoFilesView:repo_files_diff
209 # RepoFilesView:repo_archivefile
198 # RepoFilesView:repo_archivefile
210 # RepoFilesView:repo_file_raw
199 # RepoFilesView:repo_file_raw
211 # GistView:*
200 # GistView:*
212 api_access_controllers_whitelist =
201 api_access_controllers_whitelist =
213
202
214 ; Default encoding used to convert from and to unicode
203 ; Default encoding used to convert from and to unicode
215 ; can be also a comma separated list of encoding in case of mixed encodings
204 ; can be also a comma separated list of encoding in case of mixed encodings
216 default_encoding = UTF-8
205 default_encoding = UTF-8
217
206
218 ; instance-id prefix
207 ; instance-id prefix
219 ; a prefix key for this instance used for cache invalidation when running
208 ; a prefix key for this instance used for cache invalidation when running
220 ; multiple instances of RhodeCode, make sure it's globally unique for
209 ; multiple instances of RhodeCode, make sure it's globally unique for
221 ; all running RhodeCode instances. Leave empty if you don't use it
210 ; all running RhodeCode instances. Leave empty if you don't use it
222 instance_id =
211 instance_id =
223
212
224 ; Fallback authentication plugin. Set this to a plugin ID to force the usage
213 ; Fallback authentication plugin. Set this to a plugin ID to force the usage
225 ; of an authentication plugin also if it is disabled by it's settings.
214 ; of an authentication plugin also if it is disabled by it's settings.
226 ; This could be useful if you are unable to log in to the system due to broken
215 ; This could be useful if you are unable to log in to the system due to broken
227 ; authentication settings. Then you can enable e.g. the internal RhodeCode auth
216 ; authentication settings. Then you can enable e.g. the internal RhodeCode auth
228 ; module to log in again and fix the settings.
217 ; module to log in again and fix the settings.
229 ; Available builtin plugin IDs (hash is part of the ID):
218 ; Available builtin plugin IDs (hash is part of the ID):
230 ; egg:rhodecode-enterprise-ce#rhodecode
219 ; egg:rhodecode-enterprise-ce#rhodecode
231 ; egg:rhodecode-enterprise-ce#pam
220 ; egg:rhodecode-enterprise-ce#pam
232 ; egg:rhodecode-enterprise-ce#ldap
221 ; egg:rhodecode-enterprise-ce#ldap
233 ; egg:rhodecode-enterprise-ce#jasig_cas
222 ; egg:rhodecode-enterprise-ce#jasig_cas
234 ; egg:rhodecode-enterprise-ce#headers
223 ; egg:rhodecode-enterprise-ce#headers
235 ; egg:rhodecode-enterprise-ce#crowd
224 ; egg:rhodecode-enterprise-ce#crowd
236
225
237 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
226 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
238
227
239 ; Flag to control loading of legacy plugins in py:/path format
228 ; Flag to control loading of legacy plugins in py:/path format
240 auth_plugin.import_legacy_plugins = true
229 auth_plugin.import_legacy_plugins = true
241
230
242 ; alternative return HTTP header for failed authentication. Default HTTP
231 ; alternative return HTTP header for failed authentication. Default HTTP
243 ; response is 401 HTTPUnauthorized. Currently HG clients have troubles with
232 ; response is 401 HTTPUnauthorized. Currently HG clients have troubles with
244 ; handling that causing a series of failed authentication calls.
233 ; handling that causing a series of failed authentication calls.
245 ; Set this variable to 403 to return HTTPForbidden, or any other HTTP code
234 ; Set this variable to 403 to return HTTPForbidden, or any other HTTP code
246 ; This will be served instead of default 401 on bad authentication
235 ; This will be served instead of default 401 on bad authentication
247 auth_ret_code =
236 auth_ret_code =
248
237
249 ; use special detection method when serving auth_ret_code, instead of serving
238 ; use special detection method when serving auth_ret_code, instead of serving
250 ; ret_code directly, use 401 initially (Which triggers credentials prompt)
239 ; ret_code directly, use 401 initially (Which triggers credentials prompt)
251 ; and then serve auth_ret_code to clients
240 ; and then serve auth_ret_code to clients
252 auth_ret_code_detection = false
241 auth_ret_code_detection = false
253
242
254 ; locking return code. When repository is locked return this HTTP code. 2XX
243 ; locking return code. When repository is locked return this HTTP code. 2XX
255 ; codes don't break the transactions while 4XX codes do
244 ; codes don't break the transactions while 4XX codes do
256 lock_ret_code = 423
245 lock_ret_code = 423
257
246
258 ; allows to change the repository location in settings page
247 ; Filesystem location were repositories should be stored
259 allow_repo_location_change = true
248 repo_store.path = /var/opt/rhodecode_repo_store
260
249
261 ; allows to setup custom hooks in settings page
250 ; allows to setup custom hooks in settings page
262 allow_custom_hooks_settings = true
251 allow_custom_hooks_settings = true
263
252
264 ; Generated license token required for EE edition license.
253 ; Generated license token required for EE edition license.
265 ; New generated token value can be found in Admin > settings > license page.
254 ; New generated token value can be found in Admin > settings > license page.
266 license_token =
255 license_token =
267
256
268 ; This flag hides sensitive information on the license page such as token, and license data
257 ; This flag hides sensitive information on the license page such as token, and license data
269 license.hide_license_info = false
258 license.hide_license_info = false
270
259
271 ; supervisor connection uri, for managing supervisor and logs.
260 ; supervisor connection uri, for managing supervisor and logs.
272 supervisor.uri =
261 supervisor.uri =
273
262
274 ; supervisord group name/id we only want this RC instance to handle
263 ; supervisord group name/id we only want this RC instance to handle
275 supervisor.group_id = dev
264 supervisor.group_id = dev
276
265
277 ; Display extended labs settings
266 ; Display extended labs settings
278 labs_settings_active = true
267 labs_settings_active = true
279
268
280 ; Custom exception store path, defaults to TMPDIR
269 ; Custom exception store path, defaults to TMPDIR
281 ; This is used to store exception from RhodeCode in shared directory
270 ; This is used to store exception from RhodeCode in shared directory
282 #exception_tracker.store_path =
271 #exception_tracker.store_path =
283
272
284 ; Send email with exception details when it happens
273 ; Send email with exception details when it happens
285 #exception_tracker.send_email = false
274 #exception_tracker.send_email = false
286
275
287 ; Comma separated list of recipients for exception emails,
276 ; Comma separated list of recipients for exception emails,
288 ; e.g admin@rhodecode.com,devops@rhodecode.com
277 ; e.g admin@rhodecode.com,devops@rhodecode.com
289 ; Can be left empty, then emails will be sent to ALL super-admins
278 ; Can be left empty, then emails will be sent to ALL super-admins
290 #exception_tracker.send_email_recipients =
279 #exception_tracker.send_email_recipients =
291
280
292 ; optional prefix to Add to email Subject
281 ; optional prefix to Add to email Subject
293 #exception_tracker.email_prefix = [RHODECODE ERROR]
282 #exception_tracker.email_prefix = [RHODECODE ERROR]
294
283
295 ; File store configuration. This is used to store and serve uploaded files
284 ; File store configuration. This is used to store and serve uploaded files
296 file_store.enabled = true
285 file_store.enabled = true
297
286
298 ; Storage backend, available options are: local
287 ; Storage backend, available options are: local
299 file_store.backend = local
288 file_store.backend = local
300
289
301 ; path to store the uploaded binaries
290 ; path to store the uploaded binaries and artifacts
302 file_store.storage_path = %(here)s/data/file_store
291 file_store.storage_path = /var/opt/rhodecode_data/file_store
292
293
294 ; Redis url to acquire/check generation of archives locks
295 archive_cache.locking.url = redis://redis:6379/1
296
297 ; Storage backend, only 'filesystem' and 'objectstore' are available now
298 archive_cache.backend.type = filesystem
299
300 ; url for s3 compatible storage that allows to upload artifacts
301 ; e.g http://minio:9000
302 archive_cache.objectstore.url = http://s3-minio:9000
303
304 ; key for s3 auth
305 archive_cache.objectstore.key = key
306
307 ; secret for s3 auth
308 archive_cache.objectstore.secret = secret
303
309
304 ; Uncomment and set this path to control settings for archive download cache.
310 ;region for s3 storage
311 archive_cache.objectstore.region = eu-central-1
312
313 ; number of sharded buckets to create to distribute archives across
314 ; default is 8 shards
315 archive_cache.objectstore.bucket_shards = 8
316
317 ; a top-level bucket to put all other shards in
318 ; objects will be stored in rhodecode-archive-cache/shard-N based on the bucket_shards number
319 archive_cache.objectstore.bucket = rhodecode-archive-cache
320
321 ; if true, this cache will try to retry with retry_attempts=N times waiting retry_backoff time
322 archive_cache.objectstore.retry = false
323
324 ; number of seconds to wait for next try using retry
325 archive_cache.objectstore.retry_backoff = 1
326
327 ; how many tries do do a retry fetch from this backend
328 archive_cache.objectstore.retry_attempts = 10
329
330 ; Default is $cache_dir/archive_cache if not set
305 ; Generated repo archives will be cached at this location
331 ; Generated repo archives will be cached at this location
306 ; and served from the cache during subsequent requests for the same archive of
332 ; and served from the cache during subsequent requests for the same archive of
307 ; the repository. This path is important to be shared across filesystems and with
333 ; the repository. This path is important to be shared across filesystems and with
308 ; RhodeCode and vcsserver
334 ; RhodeCode and vcsserver
309
335 archive_cache.filesystem.store_dir = /var/opt/rhodecode_data/archive_cache
310 ; Default is $cache_dir/archive_cache if not set
311 archive_cache.store_dir = %(here)s/data/archive_cache
312
336
313 ; The limit in GB sets how much data we cache before recycling last used, defaults to 10 gb
337 ; The limit in GB sets how much data we cache before recycling last used, defaults to 10 gb
314 archive_cache.cache_size_gb = 10
338 archive_cache.filesystem.cache_size_gb = 1
339
340 ; Eviction policy used to clear out after cache_size_gb limit is reached
341 archive_cache.filesystem.eviction_policy = least-recently-stored
315
342
316 ; By default cache uses sharding technique, this specifies how many shards are there
343 ; By default cache uses sharding technique, this specifies how many shards are there
317 archive_cache.cache_shards = 10
344 ; default is 8 shards
345 archive_cache.filesystem.cache_shards = 8
346
347 ; if true, this cache will try to retry with retry_attempts=N times waiting retry_backoff time
348 archive_cache.filesystem.retry = false
349
350 ; number of seconds to wait for next try using retry
351 archive_cache.filesystem.retry_backoff = 1
352
353 ; how many tries do do a retry fetch from this backend
354 archive_cache.filesystem.retry_attempts = 10
355
318
356
319 ; #############
357 ; #############
320 ; CELERY CONFIG
358 ; CELERY CONFIG
321 ; #############
359 ; #############
322
360
323 ; manually run celery: /path/to/celery worker --task-events --beat --app rhodecode.lib.celerylib.loader --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler --loglevel DEBUG --ini /path/to/rhodecode.ini
361 ; manually run celery: /path/to/celery worker --task-events --beat --app rhodecode.lib.celerylib.loader --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler --loglevel DEBUG --ini /path/to/rhodecode.ini
324
362
325 use_celery = false
363 use_celery = true
326
364
327 ; path to store schedule database
365 ; path to store schedule database
328 #celerybeat-schedule.path =
366 #celerybeat-schedule.path =
329
367
330 ; connection url to the message broker (default redis)
368 ; connection url to the message broker (default redis)
331 celery.broker_url = redis://redis:6379/8
369 celery.broker_url = redis://redis:6379/8
332
370
333 ; results backend to get results for (default redis)
371 ; results backend to get results for (default redis)
334 celery.result_backend = redis://redis:6379/8
372 celery.result_backend = redis://redis:6379/8
335
373
336 ; rabbitmq example
374 ; rabbitmq example
337 #celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
375 #celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
338
376
339 ; maximum tasks to execute before worker restart
377 ; maximum tasks to execute before worker restart
340 celery.max_tasks_per_child = 20
378 celery.max_tasks_per_child = 20
341
379
342 ; tasks will never be sent to the queue, but executed locally instead.
380 ; tasks will never be sent to the queue, but executed locally instead.
343 celery.task_always_eager = false
381 celery.task_always_eager = false
344
382
345 ; #############
383 ; #############
346 ; DOGPILE CACHE
384 ; DOGPILE CACHE
347 ; #############
385 ; #############
348
386
349 ; Default cache dir for caches. Putting this into a ramdisk can boost performance.
387 ; Default cache dir for caches. Putting this into a ramdisk can boost performance.
350 ; eg. /tmpfs/data_ramdisk, however this directory might require large amount of space
388 ; eg. /tmpfs/data_ramdisk, however this directory might require large amount of space
351 cache_dir = %(here)s/data
389 cache_dir = /var/opt/rhodecode_data
352
390
353 ; *********************************************
391 ; *********************************************
354 ; `sql_cache_short` cache for heavy SQL queries
392 ; `sql_cache_short` cache for heavy SQL queries
355 ; Only supported backend is `memory_lru`
393 ; Only supported backend is `memory_lru`
356 ; *********************************************
394 ; *********************************************
357 rc_cache.sql_cache_short.backend = dogpile.cache.rc.memory_lru
395 rc_cache.sql_cache_short.backend = dogpile.cache.rc.memory_lru
358 rc_cache.sql_cache_short.expiration_time = 30
396 rc_cache.sql_cache_short.expiration_time = 30
359
397
360
398
361 ; *****************************************************
399 ; *****************************************************
362 ; `cache_repo_longterm` cache for repo object instances
400 ; `cache_repo_longterm` cache for repo object instances
363 ; Only supported backend is `memory_lru`
401 ; Only supported backend is `memory_lru`
364 ; *****************************************************
402 ; *****************************************************
365 rc_cache.cache_repo_longterm.backend = dogpile.cache.rc.memory_lru
403 rc_cache.cache_repo_longterm.backend = dogpile.cache.rc.memory_lru
366 ; by default we use 30 Days, cache is still invalidated on push
404 ; by default we use 30 Days, cache is still invalidated on push
367 rc_cache.cache_repo_longterm.expiration_time = 2592000
405 rc_cache.cache_repo_longterm.expiration_time = 2592000
368 ; max items in LRU cache, set to smaller number to save memory, and expire last used caches
406 ; max items in LRU cache, set to smaller number to save memory, and expire last used caches
369 rc_cache.cache_repo_longterm.max_size = 10000
407 rc_cache.cache_repo_longterm.max_size = 10000
370
408
371
409
372 ; *********************************************
410 ; *********************************************
373 ; `cache_general` cache for general purpose use
411 ; `cache_general` cache for general purpose use
374 ; for simplicity use rc.file_namespace backend,
412 ; for simplicity use rc.file_namespace backend,
375 ; for performance and scale use rc.redis
413 ; for performance and scale use rc.redis
376 ; *********************************************
414 ; *********************************************
377 rc_cache.cache_general.backend = dogpile.cache.rc.file_namespace
415 rc_cache.cache_general.backend = dogpile.cache.rc.file_namespace
378 rc_cache.cache_general.expiration_time = 43200
416 rc_cache.cache_general.expiration_time = 43200
379 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
417 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
380 #rc_cache.cache_general.arguments.filename = /tmp/cache_general_db
418 #rc_cache.cache_general.arguments.filename = /tmp/cache_general_db
381
419
382 ; alternative `cache_general` redis backend with distributed lock
420 ; alternative `cache_general` redis backend with distributed lock
383 #rc_cache.cache_general.backend = dogpile.cache.rc.redis
421 #rc_cache.cache_general.backend = dogpile.cache.rc.redis
384 #rc_cache.cache_general.expiration_time = 300
422 #rc_cache.cache_general.expiration_time = 300
385
423
386 ; redis_expiration_time needs to be greater then expiration_time
424 ; redis_expiration_time needs to be greater then expiration_time
387 #rc_cache.cache_general.arguments.redis_expiration_time = 7200
425 #rc_cache.cache_general.arguments.redis_expiration_time = 7200
388
426
389 #rc_cache.cache_general.arguments.host = localhost
427 #rc_cache.cache_general.arguments.host = localhost
390 #rc_cache.cache_general.arguments.port = 6379
428 #rc_cache.cache_general.arguments.port = 6379
391 #rc_cache.cache_general.arguments.db = 0
429 #rc_cache.cache_general.arguments.db = 0
392 #rc_cache.cache_general.arguments.socket_timeout = 30
430 #rc_cache.cache_general.arguments.socket_timeout = 30
393 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
431 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
394 #rc_cache.cache_general.arguments.distributed_lock = true
432 #rc_cache.cache_general.arguments.distributed_lock = true
395
433
396 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
434 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
397 #rc_cache.cache_general.arguments.lock_auto_renewal = true
435 #rc_cache.cache_general.arguments.lock_auto_renewal = true
398
436
399 ; *************************************************
437 ; *************************************************
400 ; `cache_perms` cache for permission tree, auth TTL
438 ; `cache_perms` cache for permission tree, auth TTL
401 ; for simplicity use rc.file_namespace backend,
439 ; for simplicity use rc.file_namespace backend,
402 ; for performance and scale use rc.redis
440 ; for performance and scale use rc.redis
403 ; *************************************************
441 ; *************************************************
404 rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace
442 rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace
405 rc_cache.cache_perms.expiration_time = 3600
443 rc_cache.cache_perms.expiration_time = 3600
406 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
444 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
407 #rc_cache.cache_perms.arguments.filename = /tmp/cache_perms_db
445 #rc_cache.cache_perms.arguments.filename = /tmp/cache_perms_db
408
446
409 ; alternative `cache_perms` redis backend with distributed lock
447 ; alternative `cache_perms` redis backend with distributed lock
410 #rc_cache.cache_perms.backend = dogpile.cache.rc.redis
448 #rc_cache.cache_perms.backend = dogpile.cache.rc.redis
411 #rc_cache.cache_perms.expiration_time = 300
449 #rc_cache.cache_perms.expiration_time = 300
412
450
413 ; redis_expiration_time needs to be greater then expiration_time
451 ; redis_expiration_time needs to be greater then expiration_time
414 #rc_cache.cache_perms.arguments.redis_expiration_time = 7200
452 #rc_cache.cache_perms.arguments.redis_expiration_time = 7200
415
453
416 #rc_cache.cache_perms.arguments.host = localhost
454 #rc_cache.cache_perms.arguments.host = localhost
417 #rc_cache.cache_perms.arguments.port = 6379
455 #rc_cache.cache_perms.arguments.port = 6379
418 #rc_cache.cache_perms.arguments.db = 0
456 #rc_cache.cache_perms.arguments.db = 0
419 #rc_cache.cache_perms.arguments.socket_timeout = 30
457 #rc_cache.cache_perms.arguments.socket_timeout = 30
420 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
458 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
421 #rc_cache.cache_perms.arguments.distributed_lock = true
459 #rc_cache.cache_perms.arguments.distributed_lock = true
422
460
423 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
461 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
424 #rc_cache.cache_perms.arguments.lock_auto_renewal = true
462 #rc_cache.cache_perms.arguments.lock_auto_renewal = true
425
463
426 ; ***************************************************
464 ; ***************************************************
427 ; `cache_repo` cache for file tree, Readme, RSS FEEDS
465 ; `cache_repo` cache for file tree, Readme, RSS FEEDS
428 ; for simplicity use rc.file_namespace backend,
466 ; for simplicity use rc.file_namespace backend,
429 ; for performance and scale use rc.redis
467 ; for performance and scale use rc.redis
430 ; ***************************************************
468 ; ***************************************************
431 rc_cache.cache_repo.backend = dogpile.cache.rc.file_namespace
469 rc_cache.cache_repo.backend = dogpile.cache.rc.file_namespace
432 rc_cache.cache_repo.expiration_time = 2592000
470 rc_cache.cache_repo.expiration_time = 2592000
433 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
471 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
434 #rc_cache.cache_repo.arguments.filename = /tmp/cache_repo_db
472 #rc_cache.cache_repo.arguments.filename = /tmp/cache_repo_db
435
473
436 ; alternative `cache_repo` redis backend with distributed lock
474 ; alternative `cache_repo` redis backend with distributed lock
437 #rc_cache.cache_repo.backend = dogpile.cache.rc.redis
475 #rc_cache.cache_repo.backend = dogpile.cache.rc.redis
438 #rc_cache.cache_repo.expiration_time = 2592000
476 #rc_cache.cache_repo.expiration_time = 2592000
439
477
440 ; redis_expiration_time needs to be greater then expiration_time
478 ; redis_expiration_time needs to be greater then expiration_time
441 #rc_cache.cache_repo.arguments.redis_expiration_time = 2678400
479 #rc_cache.cache_repo.arguments.redis_expiration_time = 2678400
442
480
443 #rc_cache.cache_repo.arguments.host = localhost
481 #rc_cache.cache_repo.arguments.host = localhost
444 #rc_cache.cache_repo.arguments.port = 6379
482 #rc_cache.cache_repo.arguments.port = 6379
445 #rc_cache.cache_repo.arguments.db = 1
483 #rc_cache.cache_repo.arguments.db = 1
446 #rc_cache.cache_repo.arguments.socket_timeout = 30
484 #rc_cache.cache_repo.arguments.socket_timeout = 30
447 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
485 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
448 #rc_cache.cache_repo.arguments.distributed_lock = true
486 #rc_cache.cache_repo.arguments.distributed_lock = true
449
487
450 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
488 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
451 #rc_cache.cache_repo.arguments.lock_auto_renewal = true
489 #rc_cache.cache_repo.arguments.lock_auto_renewal = true
452
490
453 ; ##############
491 ; ##############
454 ; BEAKER SESSION
492 ; BEAKER SESSION
455 ; ##############
493 ; ##############
456
494
457 ; beaker.session.type is type of storage options for the logged users sessions. Current allowed
495 ; beaker.session.type is type of storage options for the logged users sessions. Current allowed
458 ; types are file, ext:redis, ext:database, ext:memcached
496 ; types are file, ext:redis, ext:database, ext:memcached
459 ; Fastest ones are ext:redis and ext:database, DO NOT use memory type for session
497 ; Fastest ones are ext:redis and ext:database, DO NOT use memory type for session
460 beaker.session.type = file
498 #beaker.session.type = file
461 beaker.session.data_dir = %(here)s/data/sessions
499 #beaker.session.data_dir = %(here)s/data/sessions
462
500
463 ; Redis based sessions
501 ; Redis based sessions
464 #beaker.session.type = ext:redis
502 beaker.session.type = ext:redis
465 #beaker.session.url = redis://127.0.0.1:6379/2
503 beaker.session.url = redis://redis:6379/2
466
504
467 ; DB based session, fast, and allows easy management over logged in users
505 ; DB based session, fast, and allows easy management over logged in users
468 #beaker.session.type = ext:database
506 #beaker.session.type = ext:database
469 #beaker.session.table_name = db_session
507 #beaker.session.table_name = db_session
470 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
508 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
471 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
509 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
472 #beaker.session.sa.pool_recycle = 3600
510 #beaker.session.sa.pool_recycle = 3600
473 #beaker.session.sa.echo = false
511 #beaker.session.sa.echo = false
474
512
475 beaker.session.key = rhodecode
513 beaker.session.key = rhodecode
476 beaker.session.secret = develop-rc-uytcxaz
514 beaker.session.secret = develop-rc-uytcxaz
477 beaker.session.lock_dir = %(here)s/data/sessions/lock
515 beaker.session.lock_dir = /data_ramdisk/lock
478
516
479 ; Secure encrypted cookie. Requires AES and AES python libraries
517 ; Secure encrypted cookie. Requires AES and AES python libraries
480 ; you must disable beaker.session.secret to use this
518 ; you must disable beaker.session.secret to use this
481 #beaker.session.encrypt_key = key_for_encryption
519 #beaker.session.encrypt_key = key_for_encryption
482 #beaker.session.validate_key = validation_key
520 #beaker.session.validate_key = validation_key
483
521
484 ; Sets session as invalid (also logging out user) if it haven not been
522 ; Sets session as invalid (also logging out user) if it haven not been
485 ; accessed for given amount of time in seconds
523 ; accessed for given amount of time in seconds
486 beaker.session.timeout = 2592000
524 beaker.session.timeout = 2592000
487 beaker.session.httponly = true
525 beaker.session.httponly = true
488
526
489 ; Path to use for the cookie. Set to prefix if you use prefix middleware
527 ; Path to use for the cookie. Set to prefix if you use prefix middleware
490 #beaker.session.cookie_path = /custom_prefix
528 #beaker.session.cookie_path = /custom_prefix
491
529
492 ; Set https secure cookie
530 ; Set https secure cookie
493 beaker.session.secure = false
531 beaker.session.secure = false
494
532
495 ; default cookie expiration time in seconds, set to `true` to set expire
533 ; default cookie expiration time in seconds, set to `true` to set expire
496 ; at browser close
534 ; at browser close
497 #beaker.session.cookie_expires = 3600
535 #beaker.session.cookie_expires = 3600
498
536
499 ; #############################
537 ; #############################
500 ; SEARCH INDEXING CONFIGURATION
538 ; SEARCH INDEXING CONFIGURATION
501 ; #############################
539 ; #############################
502
540
503 ; Full text search indexer is available in rhodecode-tools under
541 ; Full text search indexer is available in rhodecode-tools under
504 ; `rhodecode-tools index` command
542 ; `rhodecode-tools index` command
505
543
506 ; WHOOSH Backend, doesn't require additional services to run
544 ; WHOOSH Backend, doesn't require additional services to run
507 ; it works good with few dozen repos
545 ; it works good with few dozen repos
508 search.module = rhodecode.lib.index.whoosh
546 search.module = rhodecode.lib.index.whoosh
509 search.location = %(here)s/data/index
547 search.location = %(here)s/data/index
510
548
511 ; ####################
549 ; ####################
512 ; CHANNELSTREAM CONFIG
550 ; CHANNELSTREAM CONFIG
513 ; ####################
551 ; ####################
514
552
515 ; channelstream enables persistent connections and live notification
553 ; channelstream enables persistent connections and live notification
516 ; in the system. It's also used by the chat system
554 ; in the system. It's also used by the chat system
517
555
518 channelstream.enabled = false
556 channelstream.enabled = true
519
557
520 ; server address for channelstream server on the backend
558 ; server address for channelstream server on the backend
521 channelstream.server = 127.0.0.1:9800
559 channelstream.server = channelstream:9800
522
560
523 ; location of the channelstream server from outside world
561 ; location of the channelstream server from outside world
524 ; use ws:// for http or wss:// for https. This address needs to be handled
562 ; use ws:// for http or wss:// for https. This address needs to be handled
525 ; by external HTTP server such as Nginx or Apache
563 ; by external HTTP server such as Nginx or Apache
526 ; see Nginx/Apache configuration examples in our docs
564 ; see Nginx/Apache configuration examples in our docs
527 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
565 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
528 channelstream.secret = secret
566 channelstream.secret = ENV_GENERATED
529 channelstream.history.location = %(here)s/channelstream_history
567 channelstream.history.location = /var/opt/rhodecode_data/channelstream_history
530
568
531 ; Internal application path that Javascript uses to connect into.
569 ; Internal application path that Javascript uses to connect into.
532 ; If you use proxy-prefix the prefix should be added before /_channelstream
570 ; If you use proxy-prefix the prefix should be added before /_channelstream
533 channelstream.proxy_path = /_channelstream
571 channelstream.proxy_path = /_channelstream
534
572
535
573
536 ; ##############################
574 ; ##############################
537 ; MAIN RHODECODE DATABASE CONFIG
575 ; MAIN RHODECODE DATABASE CONFIG
538 ; ##############################
576 ; ##############################
539
577
540 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
578 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
541 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
579 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
542 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode?charset=utf8
580 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode?charset=utf8
543 ; pymysql is an alternative driver for MySQL, use in case of problems with default one
581 ; pymysql is an alternative driver for MySQL, use in case of problems with default one
544 #sqlalchemy.db1.url = mysql+pymysql://root:qweqwe@localhost/rhodecode
582 #sqlalchemy.db1.url = mysql+pymysql://root:qweqwe@localhost/rhodecode
545
583
546 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
584 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
547
585
548 ; see sqlalchemy docs for other advanced settings
586 ; see sqlalchemy docs for other advanced settings
549 ; print the sql statements to output
587 ; print the sql statements to output
550 sqlalchemy.db1.echo = false
588 sqlalchemy.db1.echo = false
551
589
552 ; recycle the connections after this amount of seconds
590 ; recycle the connections after this amount of seconds
553 sqlalchemy.db1.pool_recycle = 3600
591 sqlalchemy.db1.pool_recycle = 3600
554
592
555 ; the number of connections to keep open inside the connection pool.
593 ; the number of connections to keep open inside the connection pool.
556 ; 0 indicates no limit
594 ; 0 indicates no limit
557 ; the general calculus with gevent is:
595 ; the general calculus with gevent is:
558 ; if your system allows 500 concurrent greenlets (max_connections) that all do database access,
596 ; if your system allows 500 concurrent greenlets (max_connections) that all do database access,
559 ; then increase pool size + max overflow so that they add up to 500.
597 ; then increase pool size + max overflow so that they add up to 500.
560 #sqlalchemy.db1.pool_size = 5
598 #sqlalchemy.db1.pool_size = 5
561
599
562 ; The number of connections to allow in connection pool "overflow", that is
600 ; The number of connections to allow in connection pool "overflow", that is
563 ; connections that can be opened above and beyond the pool_size setting,
601 ; connections that can be opened above and beyond the pool_size setting,
564 ; which defaults to five.
602 ; which defaults to five.
565 #sqlalchemy.db1.max_overflow = 10
603 #sqlalchemy.db1.max_overflow = 10
566
604
567 ; Connection check ping, used to detect broken database connections
605 ; Connection check ping, used to detect broken database connections
568 ; could be enabled to better handle cases if MySQL has gone away errors
606 ; could be enabled to better handle cases if MySQL has gone away errors
569 #sqlalchemy.db1.ping_connection = true
607 #sqlalchemy.db1.ping_connection = true
570
608
571 ; ##########
609 ; ##########
572 ; VCS CONFIG
610 ; VCS CONFIG
573 ; ##########
611 ; ##########
574 vcs.server.enable = true
612 vcs.server.enable = true
575 vcs.server = localhost:9900
613 vcs.server = vcsserver:10010
576
614
577 ; Web server connectivity protocol, responsible for web based VCS operations
615 ; Web server connectivity protocol, responsible for web based VCS operations
578 ; Available protocols are:
616 ; Available protocols are:
579 ; `http` - use http-rpc backend (default)
617 ; `http` - use http-rpc backend (default)
580 vcs.server.protocol = http
618 vcs.server.protocol = http
581
619
582 ; Push/Pull operations protocol, available options are:
620 ; Push/Pull operations protocol, available options are:
583 ; `http` - use http-rpc backend (default)
621 ; `http` - use http-rpc backend (default)
584 vcs.scm_app_implementation = http
622 vcs.scm_app_implementation = http
585
623
586 ; Push/Pull operations hooks protocol, available options are:
624 ; Push/Pull operations hooks protocol, available options are:
587 ; `http` - use http-rpc backend (default)
625 ; `http` - use http-rpc backend (default)
626 ; `celery` - use celery based hooks
588 vcs.hooks.protocol = http
627 vcs.hooks.protocol = http
589
628
590 ; Host on which this instance is listening for hooks. vcsserver will call this host to pull/push hooks so it should be
629 ; Host on which this instance is listening for hooks. vcsserver will call this host to pull/push hooks so it should be
591 ; accessible via network.
630 ; accessible via network.
592 ; Use vcs.hooks.host = "*" to bind to current hostname (for Docker)
631 ; Use vcs.hooks.host = "*" to bind to current hostname (for Docker)
593 vcs.hooks.host = *
632 vcs.hooks.host = *
594
633
595 ; Start VCSServer with this instance as a subprocess, useful for development
634 ; Start VCSServer with this instance as a subprocess, useful for development
596 vcs.start_server = false
635 vcs.start_server = false
597
636
598 ; List of enabled VCS backends, available options are:
637 ; List of enabled VCS backends, available options are:
599 ; `hg` - mercurial
638 ; `hg` - mercurial
600 ; `git` - git
639 ; `git` - git
601 ; `svn` - subversion
640 ; `svn` - subversion
602 vcs.backends = hg, git, svn
641 vcs.backends = hg, git, svn
603
642
604 ; Wait this number of seconds before killing connection to the vcsserver
643 ; Wait this number of seconds before killing connection to the vcsserver
605 vcs.connection_timeout = 3600
644 vcs.connection_timeout = 3600
606
645
607 ; Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
608 ; Set a numeric version for your current SVN e.g 1.8, or 1.12
609 ; Legacy available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
610 #vcs.svn.compatible_version = 1.8
611
612 ; Cache flag to cache vcsserver remote calls locally
646 ; Cache flag to cache vcsserver remote calls locally
613 ; It uses cache_region `cache_repo`
647 ; It uses cache_region `cache_repo`
614 vcs.methods.cache = true
648 vcs.methods.cache = true
615
649
616 ; ####################################################
650 ; ####################################################
617 ; Subversion proxy support (mod_dav_svn)
651 ; Subversion proxy support (mod_dav_svn)
618 ; Maps RhodeCode repo groups into SVN paths for Apache
652 ; Maps RhodeCode repo groups into SVN paths for Apache
619 ; ####################################################
653 ; ####################################################
620
654
655 ; Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
656 ; Set a numeric version for your current SVN e.g 1.8, or 1.12
657 ; Legacy available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
658 #vcs.svn.compatible_version = 1.8
659
660 ; Redis connection settings for svn integrations logic
661 ; This connection string needs to be the same on ce and vcsserver
662 vcs.svn.redis_conn = redis://redis:6379/0
663
664 ; Enable SVN proxy of requests over HTTP
665 vcs.svn.proxy.enabled = true
666
667 ; host to connect to running SVN subsystem
668 vcs.svn.proxy.host = http://svn:8090
669
621 ; Enable or disable the config file generation.
670 ; Enable or disable the config file generation.
622 svn.proxy.generate_config = false
671 svn.proxy.generate_config = true
623
672
624 ; Generate config file with `SVNListParentPath` set to `On`.
673 ; Generate config file with `SVNListParentPath` set to `On`.
625 svn.proxy.list_parent_path = true
674 svn.proxy.list_parent_path = true
626
675
627 ; Set location and file name of generated config file.
676 ; Set location and file name of generated config file.
628 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
677 svn.proxy.config_file_path = /etc/rhodecode/conf/svn/mod_dav_svn.conf
629
678
630 ; alternative mod_dav config template. This needs to be a valid mako template
679 ; alternative mod_dav config template. This needs to be a valid mako template
631 ; Example template can be found in the source code:
680 ; Example template can be found in the source code:
632 ; rhodecode/apps/svn_support/templates/mod-dav-svn.conf.mako
681 ; rhodecode/apps/svn_support/templates/mod-dav-svn.conf.mako
633 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
682 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
634
683
635 ; Used as a prefix to the `Location` block in the generated config file.
684 ; Used as a prefix to the `Location` block in the generated config file.
636 ; In most cases it should be set to `/`.
685 ; In most cases it should be set to `/`.
637 svn.proxy.location_root = /
686 svn.proxy.location_root = /
638
687
639 ; Command to reload the mod dav svn configuration on change.
688 ; Command to reload the mod dav svn configuration on change.
640 ; Example: `/etc/init.d/apache2 reload` or /home/USER/apache_reload.sh
689 ; Example: `/etc/init.d/apache2 reload` or /home/USER/apache_reload.sh
641 ; Make sure user who runs RhodeCode process is allowed to reload Apache
690 ; Make sure user who runs RhodeCode process is allowed to reload Apache
642 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
691 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
643
692
644 ; If the timeout expires before the reload command finishes, the command will
693 ; If the timeout expires before the reload command finishes, the command will
645 ; be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
694 ; be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
646 #svn.proxy.reload_timeout = 10
695 #svn.proxy.reload_timeout = 10
647
696
648 ; ####################
697 ; ####################
649 ; SSH Support Settings
698 ; SSH Support Settings
650 ; ####################
699 ; ####################
651
700
652 ; Defines if a custom authorized_keys file should be created and written on
701 ; Defines if a custom authorized_keys file should be created and written on
653 ; any change user ssh keys. Setting this to false also disables possibility
702 ; any change user ssh keys. Setting this to false also disables possibility
654 ; of adding SSH keys by users from web interface. Super admins can still
703 ; of adding SSH keys by users from web interface. Super admins can still
655 ; manage SSH Keys.
704 ; manage SSH Keys.
656 ssh.generate_authorized_keyfile = false
705 ssh.generate_authorized_keyfile = true
657
706
658 ; Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
707 ; Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
659 # ssh.authorized_keys_ssh_opts =
708 # ssh.authorized_keys_ssh_opts =
660
709
661 ; Path to the authorized_keys file where the generate entries are placed.
710 ; Path to the authorized_keys file where the generate entries are placed.
662 ; It is possible to have multiple key files specified in `sshd_config` e.g.
711 ; It is possible to have multiple key files specified in `sshd_config` e.g.
663 ; AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
712 ; AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
664 ssh.authorized_keys_file_path = ~/.ssh/authorized_keys_rhodecode
713 ssh.authorized_keys_file_path = /etc/rhodecode/conf/ssh/authorized_keys_rhodecode
665
714
666 ; Command to execute the SSH wrapper. The binary is available in the
715 ; Command to execute the SSH wrapper. The binary is available in the
667 ; RhodeCode installation directory.
716 ; RhodeCode installation directory.
668 ; e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
717 ; legacy: /usr/local/bin/rhodecode_bin/bin/rc-ssh-wrapper
669 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
718 ; new rewrite: /usr/local/bin/rhodecode_bin/bin/rc-ssh-wrapper-v2
719 ssh.wrapper_cmd = /usr/local/bin/rhodecode_bin/bin/rc-ssh-wrapper
670
720
671 ; Allow shell when executing the ssh-wrapper command
721 ; Allow shell when executing the ssh-wrapper command
672 ssh.wrapper_cmd_allow_shell = false
722 ssh.wrapper_cmd_allow_shell = false
673
723
674 ; Enables logging, and detailed output send back to the client during SSH
724 ; Enables logging, and detailed output send back to the client during SSH
675 ; operations. Useful for debugging, shouldn't be used in production.
725 ; operations. Useful for debugging, shouldn't be used in production.
676 ssh.enable_debug_logging = true
726 ssh.enable_debug_logging = true
677
727
678 ; Paths to binary executable, by default they are the names, but we can
728 ; Paths to binary executable, by default they are the names, but we can
679 ; override them if we want to use a custom one
729 ; override them if we want to use a custom one
680 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
730 ssh.executable.hg = /usr/local/bin/rhodecode_bin/vcs_bin/hg
681 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
731 ssh.executable.git = /usr/local/bin/rhodecode_bin/vcs_bin/git
682 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
732 ssh.executable.svn = /usr/local/bin/rhodecode_bin/vcs_bin/svnserve
683
733
684 ; Enables SSH key generator web interface. Disabling this still allows users
734 ; Enables SSH key generator web interface. Disabling this still allows users
685 ; to add their own keys.
735 ; to add their own keys.
686 ssh.enable_ui_key_generator = true
736 ssh.enable_ui_key_generator = true
687
737
688
689 ; #################
690 ; APPENLIGHT CONFIG
691 ; #################
692
693 ; Appenlight is tailored to work with RhodeCode, see
694 ; http://appenlight.rhodecode.com for details how to obtain an account
695
696 ; Appenlight integration enabled
697 #appenlight = false
698
699 #appenlight.server_url = https://api.appenlight.com
700 #appenlight.api_key = YOUR_API_KEY
701 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
702
703 ; used for JS client
704 #appenlight.api_public_key = YOUR_API_PUBLIC_KEY
705
706 ; TWEAK AMOUNT OF INFO SENT HERE
707
708 ; enables 404 error logging (default False)
709 #appenlight.report_404 = false
710
711 ; time in seconds after request is considered being slow (default 1)
712 #appenlight.slow_request_time = 1
713
714 ; record slow requests in application
715 ; (needs to be enabled for slow datastore recording and time tracking)
716 #appenlight.slow_requests = true
717
718 ; enable hooking to application loggers
719 #appenlight.logging = true
720
721 ; minimum log level for log capture
722 #ppenlight.logging.level = WARNING
723
724 ; send logs only from erroneous/slow requests
725 ; (saves API quota for intensive logging)
726 #appenlight.logging_on_error = false
727
728 ; list of additional keywords that should be grabbed from environ object
729 ; can be string with comma separated list of words in lowercase
730 ; (by default client will always send following info:
731 ; 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
732 ; start with HTTP* this list be extended with additional keywords here
733 #appenlight.environ_keys_whitelist =
734
735 ; list of keywords that should be blanked from request object
736 ; can be string with comma separated list of words in lowercase
737 ; (by default client will always blank keys that contain following words
738 ; 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
739 ; this list be extended with additional keywords set here
740 #appenlight.request_keys_blacklist =
741
742 ; list of namespaces that should be ignores when gathering log entries
743 ; can be string with comma separated list of namespaces
744 ; (by default the client ignores own entries: appenlight_client.client)
745 #appenlight.log_namespace_blacklist =
746
747 ; Statsd client config, this is used to send metrics to statsd
738 ; Statsd client config, this is used to send metrics to statsd
748 ; We recommend setting statsd_exported and scrape them using Prometheus
739 ; We recommend setting statsd_exported and scrape them using Prometheus
749 #statsd.enabled = false
740 #statsd.enabled = false
750 #statsd.statsd_host = 0.0.0.0
741 #statsd.statsd_host = 0.0.0.0
751 #statsd.statsd_port = 8125
742 #statsd.statsd_port = 8125
752 #statsd.statsd_prefix =
743 #statsd.statsd_prefix =
753 #statsd.statsd_ipv6 = false
744 #statsd.statsd_ipv6 = false
754
745
755 ; configure logging automatically at server startup set to false
746 ; configure logging automatically at server startup set to false
756 ; to use the below custom logging config.
747 ; to use the below custom logging config.
757 ; RC_LOGGING_FORMATTER
748 ; RC_LOGGING_FORMATTER
758 ; RC_LOGGING_LEVEL
749 ; RC_LOGGING_LEVEL
759 ; env variables can control the settings for logging in case of autoconfigure
750 ; env variables can control the settings for logging in case of autoconfigure
760
751
761 #logging.autoconfigure = true
752 #logging.autoconfigure = true
762
753
763 ; specify your own custom logging config file to configure logging
754 ; specify your own custom logging config file to configure logging
764 #logging.logging_conf_file = /path/to/custom_logging.ini
755 #logging.logging_conf_file = /path/to/custom_logging.ini
765
756
766 ; Dummy marker to add new entries after.
757 ; Dummy marker to add new entries after.
767 ; Add any custom entries below. Please don't remove this marker.
758 ; Add any custom entries below. Please don't remove this marker.
768 custom.conf = 1
759 custom.conf = 1
769
760
770
761
771 ; #####################
762 ; #####################
772 ; LOGGING CONFIGURATION
763 ; LOGGING CONFIGURATION
773 ; #####################
764 ; #####################
774
765
775 [loggers]
766 [loggers]
776 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
767 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
777
768
778 [handlers]
769 [handlers]
779 keys = console, console_sql
770 keys = console, console_sql
780
771
781 [formatters]
772 [formatters]
782 keys = generic, json, color_formatter, color_formatter_sql
773 keys = generic, json, color_formatter, color_formatter_sql
783
774
784 ; #######
775 ; #######
785 ; LOGGERS
776 ; LOGGERS
786 ; #######
777 ; #######
787 [logger_root]
778 [logger_root]
788 level = NOTSET
779 level = NOTSET
789 handlers = console
780 handlers = console
790
781
791 [logger_sqlalchemy]
782 [logger_sqlalchemy]
792 level = INFO
783 level = INFO
793 handlers = console_sql
784 handlers = console_sql
794 qualname = sqlalchemy.engine
785 qualname = sqlalchemy.engine
795 propagate = 0
786 propagate = 0
796
787
797 [logger_beaker]
788 [logger_beaker]
798 level = DEBUG
789 level = DEBUG
799 handlers =
790 handlers =
800 qualname = beaker.container
791 qualname = beaker.container
801 propagate = 1
792 propagate = 1
802
793
803 [logger_rhodecode]
794 [logger_rhodecode]
804 level = DEBUG
795 level = DEBUG
805 handlers =
796 handlers =
806 qualname = rhodecode
797 qualname = rhodecode
807 propagate = 1
798 propagate = 1
808
799
809 [logger_ssh_wrapper]
800 [logger_ssh_wrapper]
810 level = DEBUG
801 level = DEBUG
811 handlers =
802 handlers =
812 qualname = ssh_wrapper
803 qualname = ssh_wrapper
813 propagate = 1
804 propagate = 1
814
805
815 [logger_celery]
806 [logger_celery]
816 level = DEBUG
807 level = DEBUG
817 handlers =
808 handlers =
818 qualname = celery
809 qualname = celery
819
810
820
811
821 ; ########
812 ; ########
822 ; HANDLERS
813 ; HANDLERS
823 ; ########
814 ; ########
824
815
825 [handler_console]
816 [handler_console]
826 class = StreamHandler
817 class = StreamHandler
827 args = (sys.stderr, )
818 args = (sys.stderr, )
828 level = DEBUG
819 level = DEBUG
829 ; To enable JSON formatted logs replace 'generic/color_formatter' with 'json'
820 ; To enable JSON formatted logs replace 'generic/color_formatter' with 'json'
830 ; This allows sending properly formatted logs to grafana loki or elasticsearch
821 ; This allows sending properly formatted logs to grafana loki or elasticsearch
831 formatter = color_formatter
822 formatter = color_formatter
832
823
833 [handler_console_sql]
824 [handler_console_sql]
834 ; "level = DEBUG" logs SQL queries and results.
825 ; "level = DEBUG" logs SQL queries and results.
835 ; "level = INFO" logs SQL queries.
826 ; "level = INFO" logs SQL queries.
836 ; "level = WARN" logs neither. (Recommended for production systems.)
827 ; "level = WARN" logs neither. (Recommended for production systems.)
837 class = StreamHandler
828 class = StreamHandler
838 args = (sys.stderr, )
829 args = (sys.stderr, )
839 level = WARN
830 level = WARN
840 ; To enable JSON formatted logs replace 'generic/color_formatter_sql' with 'json'
831 ; To enable JSON formatted logs replace 'generic/color_formatter_sql' with 'json'
841 ; This allows sending properly formatted logs to grafana loki or elasticsearch
832 ; This allows sending properly formatted logs to grafana loki or elasticsearch
842 formatter = color_formatter_sql
833 formatter = color_formatter_sql
843
834
844 ; ##########
835 ; ##########
845 ; FORMATTERS
836 ; FORMATTERS
846 ; ##########
837 ; ##########
847
838
848 [formatter_generic]
839 [formatter_generic]
849 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
840 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
850 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
841 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
851 datefmt = %Y-%m-%d %H:%M:%S
842 datefmt = %Y-%m-%d %H:%M:%S
852
843
853 [formatter_color_formatter]
844 [formatter_color_formatter]
854 class = rhodecode.lib.logging_formatter.ColorFormatter
845 class = rhodecode.lib.logging_formatter.ColorFormatter
855 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
846 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
856 datefmt = %Y-%m-%d %H:%M:%S
847 datefmt = %Y-%m-%d %H:%M:%S
857
848
858 [formatter_color_formatter_sql]
849 [formatter_color_formatter_sql]
859 class = rhodecode.lib.logging_formatter.ColorFormatterSql
850 class = rhodecode.lib.logging_formatter.ColorFormatterSql
860 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
851 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
861 datefmt = %Y-%m-%d %H:%M:%S
852 datefmt = %Y-%m-%d %H:%M:%S
862
853
863 [formatter_json]
854 [formatter_json]
864 format = %(timestamp)s %(levelname)s %(name)s %(message)s %(req_id)s
855 format = %(timestamp)s %(levelname)s %(name)s %(message)s %(req_id)s
865 class = rhodecode.lib._vendor.jsonlogger.JsonFormatter
856 class = rhodecode.lib._vendor.jsonlogger.JsonFormatter
@@ -1,816 +1,824 b''
1
1
2 ; #########################################
2 ; #########################################
3 ; RHODECODE COMMUNITY EDITION CONFIGURATION
3 ; RHODECODE COMMUNITY EDITION CONFIGURATION
4 ; #########################################
4 ; #########################################
5
5
6 [DEFAULT]
6 [DEFAULT]
7 ; Debug flag sets all loggers to debug, and enables request tracking
7 ; Debug flag sets all loggers to debug, and enables request tracking
8 debug = false
8 debug = false
9
9
10 ; ########################################################################
10 ; ########################################################################
11 ; EMAIL CONFIGURATION
11 ; EMAIL CONFIGURATION
12 ; These settings will be used by the RhodeCode mailing system
12 ; These settings will be used by the RhodeCode mailing system
13 ; ########################################################################
13 ; ########################################################################
14
14
15 ; prefix all emails subjects with given prefix, helps filtering out emails
15 ; prefix all emails subjects with given prefix, helps filtering out emails
16 #email_prefix = [RhodeCode]
16 #email_prefix = [RhodeCode]
17
17
18 ; email FROM address all mails will be sent
18 ; email FROM address all mails will be sent
19 #app_email_from = rhodecode-noreply@localhost
19 #app_email_from = rhodecode-noreply@localhost
20
20
21 #smtp_server = mail.server.com
21 #smtp_server = mail.server.com
22 #smtp_username =
22 #smtp_username =
23 #smtp_password =
23 #smtp_password =
24 #smtp_port =
24 #smtp_port =
25 #smtp_use_tls = false
25 #smtp_use_tls = false
26 #smtp_use_ssl = true
26 #smtp_use_ssl = true
27
27
28 [server:main]
28 [server:main]
29 ; COMMON HOST/IP CONFIG, This applies mostly to develop setup,
29 ; COMMON HOST/IP CONFIG, This applies mostly to develop setup,
30 ; Host port for gunicorn are controlled by gunicorn_conf.py
30 ; Host port for gunicorn are controlled by gunicorn_conf.py
31 host = 127.0.0.1
31 host = 127.0.0.1
32 port = 10020
32 port = 10020
33
33
34
34
35 ; ###########################
35 ; ###########################
36 ; GUNICORN APPLICATION SERVER
36 ; GUNICORN APPLICATION SERVER
37 ; ###########################
37 ; ###########################
38
38
39 ; run with gunicorn --paste rhodecode.ini --config gunicorn_conf.py
39 ; run with gunicorn --config gunicorn_conf.py --paste rhodecode.ini
40
40
41 ; Module to use, this setting shouldn't be changed
41 ; Module to use, this setting shouldn't be changed
42 use = egg:gunicorn#main
42 use = egg:gunicorn#main
43
43
44 ; Prefix middleware for RhodeCode.
44 ; Prefix middleware for RhodeCode.
45 ; recommended when using proxy setup.
45 ; recommended when using proxy setup.
46 ; allows to set RhodeCode under a prefix in server.
46 ; allows to set RhodeCode under a prefix in server.
47 ; eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
47 ; eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
48 ; And set your prefix like: `prefix = /custom_prefix`
48 ; And set your prefix like: `prefix = /custom_prefix`
49 ; be sure to also set beaker.session.cookie_path = /custom_prefix if you need
49 ; be sure to also set beaker.session.cookie_path = /custom_prefix if you need
50 ; to make your cookies only work on prefix url
50 ; to make your cookies only work on prefix url
51 [filter:proxy-prefix]
51 [filter:proxy-prefix]
52 use = egg:PasteDeploy#prefix
52 use = egg:PasteDeploy#prefix
53 prefix = /
53 prefix = /
54
54
55 [app:main]
55 [app:main]
56 ; The %(here)s variable will be replaced with the absolute path of parent directory
56 ; The %(here)s variable will be replaced with the absolute path of parent directory
57 ; of this file
57 ; of this file
58 ; Each option in the app:main can be override by an environmental variable
58 ; Each option in the app:main can be override by an environmental variable
59 ;
59 ;
60 ;To override an option:
60 ;To override an option:
61 ;
61 ;
62 ;RC_<KeyName>
62 ;RC_<KeyName>
63 ;Everything should be uppercase, . and - should be replaced by _.
63 ;Everything should be uppercase, . and - should be replaced by _.
64 ;For example, if you have these configuration settings:
64 ;For example, if you have these configuration settings:
65 ;rc_cache.repo_object.backend = foo
65 ;rc_cache.repo_object.backend = foo
66 ;can be overridden by
66 ;can be overridden by
67 ;export RC_CACHE_REPO_OBJECT_BACKEND=foo
67 ;export RC_CACHE_REPO_OBJECT_BACKEND=foo
68
68
69 use = egg:rhodecode-enterprise-ce
69 use = egg:rhodecode-enterprise-ce
70
70
71 ; enable proxy prefix middleware, defined above
71 ; enable proxy prefix middleware, defined above
72 #filter-with = proxy-prefix
72 #filter-with = proxy-prefix
73
73
74 ; encryption key used to encrypt social plugin tokens,
74 ; encryption key used to encrypt social plugin tokens,
75 ; remote_urls with credentials etc, if not set it defaults to
75 ; remote_urls with credentials etc, if not set it defaults to
76 ; `beaker.session.secret`
76 ; `beaker.session.secret`
77 #rhodecode.encrypted_values.secret =
77 #rhodecode.encrypted_values.secret =
78
78
79 ; decryption strict mode (enabled by default). It controls if decryption raises
79 ; decryption strict mode (enabled by default). It controls if decryption raises
80 ; `SignatureVerificationError` in case of wrong key, or damaged encryption data.
80 ; `SignatureVerificationError` in case of wrong key, or damaged encryption data.
81 #rhodecode.encrypted_values.strict = false
81 #rhodecode.encrypted_values.strict = false
82
82
83 ; Pick algorithm for encryption. Either fernet (more secure) or aes (default)
83 ; Pick algorithm for encryption. Either fernet (more secure) or aes (default)
84 ; fernet is safer, and we strongly recommend switching to it.
84 ; fernet is safer, and we strongly recommend switching to it.
85 ; Due to backward compatibility aes is used as default.
85 ; Due to backward compatibility aes is used as default.
86 #rhodecode.encrypted_values.algorithm = fernet
86 #rhodecode.encrypted_values.algorithm = fernet
87
87
88 ; Return gzipped responses from RhodeCode (static files/application)
88 ; Return gzipped responses from RhodeCode (static files/application)
89 gzip_responses = false
89 gzip_responses = false
90
90
91 ; Auto-generate javascript routes file on startup
91 ; Auto-generate javascript routes file on startup
92 generate_js_files = false
92 generate_js_files = false
93
93
94 ; System global default language.
94 ; System global default language.
95 ; All available languages: en (default), be, de, es, fr, it, ja, pl, pt, ru, zh
95 ; All available languages: en (default), be, de, es, fr, it, ja, pl, pt, ru, zh
96 lang = en
96 lang = en
97
97
98 ; Perform a full repository scan and import on each server start.
98 ; Perform a full repository scan and import on each server start.
99 ; Settings this to true could lead to very long startup time.
99 ; Settings this to true could lead to very long startup time.
100 startup.import_repos = false
100 startup.import_repos = false
101
101
102 ; URL at which the application is running. This is used for Bootstrapping
102 ; URL at which the application is running. This is used for Bootstrapping
103 ; requests in context when no web request is available. Used in ishell, or
103 ; requests in context when no web request is available. Used in ishell, or
104 ; SSH calls. Set this for events to receive proper url for SSH calls.
104 ; SSH calls. Set this for events to receive proper url for SSH calls.
105 app.base_url = http://rhodecode.local
105 app.base_url = http://rhodecode.local
106
106
107 ; Host at which the Service API is running.
108 app.service_api.host = http://rhodecode.local:10020
109
110 ; Secret for Service API authentication.
111 app.service_api.token =
112
107 ; Unique application ID. Should be a random unique string for security.
113 ; Unique application ID. Should be a random unique string for security.
108 app_instance_uuid = rc-production
114 app_instance_uuid = rc-production
109
115
110 ; Cut off limit for large diffs (size in bytes). If overall diff size on
116 ; Cut off limit for large diffs (size in bytes). If overall diff size on
111 ; commit, or pull request exceeds this limit this diff will be displayed
117 ; commit, or pull request exceeds this limit this diff will be displayed
112 ; partially. E.g 512000 == 512Kb
118 ; partially. E.g 512000 == 512Kb
113 cut_off_limit_diff = 512000
119 cut_off_limit_diff = 512000
114
120
115 ; Cut off limit for large files inside diffs (size in bytes). Each individual
121 ; Cut off limit for large files inside diffs (size in bytes). Each individual
116 ; file inside diff which exceeds this limit will be displayed partially.
122 ; file inside diff which exceeds this limit will be displayed partially.
117 ; E.g 128000 == 128Kb
123 ; E.g 128000 == 128Kb
118 cut_off_limit_file = 128000
124 cut_off_limit_file = 128000
119
125
120 ; Use cached version of vcs repositories everywhere. Recommended to be `true`
126 ; Use cached version of vcs repositories everywhere. Recommended to be `true`
121 vcs_full_cache = true
127 vcs_full_cache = true
122
128
123 ; Force https in RhodeCode, fixes https redirects, assumes it's always https.
129 ; Force https in RhodeCode, fixes https redirects, assumes it's always https.
124 ; Normally this is controlled by proper flags sent from http server such as Nginx or Apache
130 ; Normally this is controlled by proper flags sent from http server such as Nginx or Apache
125 force_https = false
131 force_https = false
126
132
127 ; use Strict-Transport-Security headers
133 ; use Strict-Transport-Security headers
128 use_htsts = false
134 use_htsts = false
129
135
130 ; Set to true if your repos are exposed using the dumb protocol
136 ; Set to true if your repos are exposed using the dumb protocol
131 git_update_server_info = false
137 git_update_server_info = false
132
138
133 ; RSS/ATOM feed options
139 ; RSS/ATOM feed options
134 rss_cut_off_limit = 256000
140 rss_cut_off_limit = 256000
135 rss_items_per_page = 10
141 rss_items_per_page = 10
136 rss_include_diff = false
142 rss_include_diff = false
137
143
138 ; gist URL alias, used to create nicer urls for gist. This should be an
144 ; gist URL alias, used to create nicer urls for gist. This should be an
139 ; url that does rewrites to _admin/gists/{gistid}.
145 ; url that does rewrites to _admin/gists/{gistid}.
140 ; example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
146 ; example: http://gist.rhodecode.org/{gistid}. Empty means use the internal
141 ; RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
147 ; RhodeCode url, ie. http[s]://rhodecode.server/_admin/gists/{gistid}
142 gist_alias_url =
148 gist_alias_url =
143
149
144 ; List of views (using glob pattern syntax) that AUTH TOKENS could be
150 ; List of views (using glob pattern syntax) that AUTH TOKENS could be
145 ; used for access.
151 ; used for access.
146 ; Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
152 ; Adding ?auth_token=TOKEN_HASH to the url authenticates this request as if it
147 ; came from the the logged in user who own this authentication token.
153 ; came from the the logged in user who own this authentication token.
148 ; Additionally @TOKEN syntax can be used to bound the view to specific
154 ; Additionally @TOKEN syntax can be used to bound the view to specific
149 ; authentication token. Such view would be only accessible when used together
155 ; authentication token. Such view would be only accessible when used together
150 ; with this authentication token
156 ; with this authentication token
151 ; list of all views can be found under `/_admin/permissions/auth_token_access`
157 ; list of all views can be found under `/_admin/permissions/auth_token_access`
152 ; The list should be "," separated and on a single line.
158 ; The list should be "," separated and on a single line.
153 ; Most common views to enable:
159 ; Most common views to enable:
154
160
155 # RepoCommitsView:repo_commit_download
161 # RepoCommitsView:repo_commit_download
156 # RepoCommitsView:repo_commit_patch
162 # RepoCommitsView:repo_commit_patch
157 # RepoCommitsView:repo_commit_raw
163 # RepoCommitsView:repo_commit_raw
158 # RepoCommitsView:repo_commit_raw@TOKEN
164 # RepoCommitsView:repo_commit_raw@TOKEN
159 # RepoFilesView:repo_files_diff
165 # RepoFilesView:repo_files_diff
160 # RepoFilesView:repo_archivefile
166 # RepoFilesView:repo_archivefile
161 # RepoFilesView:repo_file_raw
167 # RepoFilesView:repo_file_raw
162 # GistView:*
168 # GistView:*
163 api_access_controllers_whitelist =
169 api_access_controllers_whitelist =
164
170
165 ; Default encoding used to convert from and to unicode
171 ; Default encoding used to convert from and to unicode
166 ; can be also a comma separated list of encoding in case of mixed encodings
172 ; can be also a comma separated list of encoding in case of mixed encodings
167 default_encoding = UTF-8
173 default_encoding = UTF-8
168
174
169 ; instance-id prefix
175 ; instance-id prefix
170 ; a prefix key for this instance used for cache invalidation when running
176 ; a prefix key for this instance used for cache invalidation when running
171 ; multiple instances of RhodeCode, make sure it's globally unique for
177 ; multiple instances of RhodeCode, make sure it's globally unique for
172 ; all running RhodeCode instances. Leave empty if you don't use it
178 ; all running RhodeCode instances. Leave empty if you don't use it
173 instance_id =
179 instance_id =
174
180
175 ; Fallback authentication plugin. Set this to a plugin ID to force the usage
181 ; Fallback authentication plugin. Set this to a plugin ID to force the usage
176 ; of an authentication plugin also if it is disabled by it's settings.
182 ; of an authentication plugin also if it is disabled by it's settings.
177 ; This could be useful if you are unable to log in to the system due to broken
183 ; This could be useful if you are unable to log in to the system due to broken
178 ; authentication settings. Then you can enable e.g. the internal RhodeCode auth
184 ; authentication settings. Then you can enable e.g. the internal RhodeCode auth
179 ; module to log in again and fix the settings.
185 ; module to log in again and fix the settings.
180 ; Available builtin plugin IDs (hash is part of the ID):
186 ; Available builtin plugin IDs (hash is part of the ID):
181 ; egg:rhodecode-enterprise-ce#rhodecode
187 ; egg:rhodecode-enterprise-ce#rhodecode
182 ; egg:rhodecode-enterprise-ce#pam
188 ; egg:rhodecode-enterprise-ce#pam
183 ; egg:rhodecode-enterprise-ce#ldap
189 ; egg:rhodecode-enterprise-ce#ldap
184 ; egg:rhodecode-enterprise-ce#jasig_cas
190 ; egg:rhodecode-enterprise-ce#jasig_cas
185 ; egg:rhodecode-enterprise-ce#headers
191 ; egg:rhodecode-enterprise-ce#headers
186 ; egg:rhodecode-enterprise-ce#crowd
192 ; egg:rhodecode-enterprise-ce#crowd
187
193
188 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
194 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
189
195
190 ; Flag to control loading of legacy plugins in py:/path format
196 ; Flag to control loading of legacy plugins in py:/path format
191 auth_plugin.import_legacy_plugins = true
197 auth_plugin.import_legacy_plugins = true
192
198
193 ; alternative return HTTP header for failed authentication. Default HTTP
199 ; alternative return HTTP header for failed authentication. Default HTTP
194 ; response is 401 HTTPUnauthorized. Currently HG clients have troubles with
200 ; response is 401 HTTPUnauthorized. Currently HG clients have troubles with
195 ; handling that causing a series of failed authentication calls.
201 ; handling that causing a series of failed authentication calls.
196 ; Set this variable to 403 to return HTTPForbidden, or any other HTTP code
202 ; Set this variable to 403 to return HTTPForbidden, or any other HTTP code
197 ; This will be served instead of default 401 on bad authentication
203 ; This will be served instead of default 401 on bad authentication
198 auth_ret_code =
204 auth_ret_code =
199
205
200 ; use special detection method when serving auth_ret_code, instead of serving
206 ; use special detection method when serving auth_ret_code, instead of serving
201 ; ret_code directly, use 401 initially (Which triggers credentials prompt)
207 ; ret_code directly, use 401 initially (Which triggers credentials prompt)
202 ; and then serve auth_ret_code to clients
208 ; and then serve auth_ret_code to clients
203 auth_ret_code_detection = false
209 auth_ret_code_detection = false
204
210
205 ; locking return code. When repository is locked return this HTTP code. 2XX
211 ; locking return code. When repository is locked return this HTTP code. 2XX
206 ; codes don't break the transactions while 4XX codes do
212 ; codes don't break the transactions while 4XX codes do
207 lock_ret_code = 423
213 lock_ret_code = 423
208
214
209 ; allows to change the repository location in settings page
215 ; Filesystem location were repositories should be stored
210 allow_repo_location_change = true
216 repo_store.path = /var/opt/rhodecode_repo_store
211
217
212 ; allows to setup custom hooks in settings page
218 ; allows to setup custom hooks in settings page
213 allow_custom_hooks_settings = true
219 allow_custom_hooks_settings = true
214
220
215 ; Generated license token required for EE edition license.
221 ; Generated license token required for EE edition license.
216 ; New generated token value can be found in Admin > settings > license page.
222 ; New generated token value can be found in Admin > settings > license page.
217 license_token =
223 license_token =
218
224
219 ; This flag hides sensitive information on the license page such as token, and license data
225 ; This flag hides sensitive information on the license page such as token, and license data
220 license.hide_license_info = false
226 license.hide_license_info = false
221
227
222 ; supervisor connection uri, for managing supervisor and logs.
228 ; supervisor connection uri, for managing supervisor and logs.
223 supervisor.uri =
229 supervisor.uri =
224
230
225 ; supervisord group name/id we only want this RC instance to handle
231 ; supervisord group name/id we only want this RC instance to handle
226 supervisor.group_id = prod
232 supervisor.group_id = prod
227
233
228 ; Display extended labs settings
234 ; Display extended labs settings
229 labs_settings_active = true
235 labs_settings_active = true
230
236
231 ; Custom exception store path, defaults to TMPDIR
237 ; Custom exception store path, defaults to TMPDIR
232 ; This is used to store exception from RhodeCode in shared directory
238 ; This is used to store exception from RhodeCode in shared directory
233 #exception_tracker.store_path =
239 #exception_tracker.store_path =
234
240
235 ; Send email with exception details when it happens
241 ; Send email with exception details when it happens
236 #exception_tracker.send_email = false
242 #exception_tracker.send_email = false
237
243
238 ; Comma separated list of recipients for exception emails,
244 ; Comma separated list of recipients for exception emails,
239 ; e.g admin@rhodecode.com,devops@rhodecode.com
245 ; e.g admin@rhodecode.com,devops@rhodecode.com
240 ; Can be left empty, then emails will be sent to ALL super-admins
246 ; Can be left empty, then emails will be sent to ALL super-admins
241 #exception_tracker.send_email_recipients =
247 #exception_tracker.send_email_recipients =
242
248
243 ; optional prefix to Add to email Subject
249 ; optional prefix to Add to email Subject
244 #exception_tracker.email_prefix = [RHODECODE ERROR]
250 #exception_tracker.email_prefix = [RHODECODE ERROR]
245
251
246 ; File store configuration. This is used to store and serve uploaded files
252 ; File store configuration. This is used to store and serve uploaded files
247 file_store.enabled = true
253 file_store.enabled = true
248
254
249 ; Storage backend, available options are: local
255 ; Storage backend, available options are: local
250 file_store.backend = local
256 file_store.backend = local
251
257
252 ; path to store the uploaded binaries
258 ; path to store the uploaded binaries and artifacts
253 file_store.storage_path = %(here)s/data/file_store
259 file_store.storage_path = /var/opt/rhodecode_data/file_store
260
261
262 ; Redis url to acquire/check generation of archives locks
263 archive_cache.locking.url = redis://redis:6379/1
264
265 ; Storage backend, only 'filesystem' and 'objectstore' are available now
266 archive_cache.backend.type = filesystem
267
268 ; url for s3 compatible storage that allows to upload artifacts
269 ; e.g http://minio:9000
270 archive_cache.objectstore.url = http://s3-minio:9000
271
272 ; key for s3 auth
273 archive_cache.objectstore.key = key
274
275 ; secret for s3 auth
276 archive_cache.objectstore.secret = secret
254
277
255 ; Uncomment and set this path to control settings for archive download cache.
278 ;region for s3 storage
279 archive_cache.objectstore.region = eu-central-1
280
281 ; number of sharded buckets to create to distribute archives across
282 ; default is 8 shards
283 archive_cache.objectstore.bucket_shards = 8
284
285 ; a top-level bucket to put all other shards in
286 ; objects will be stored in rhodecode-archive-cache/shard-N based on the bucket_shards number
287 archive_cache.objectstore.bucket = rhodecode-archive-cache
288
289 ; if true, this cache will try to retry with retry_attempts=N times waiting retry_backoff time
290 archive_cache.objectstore.retry = false
291
292 ; number of seconds to wait for next try using retry
293 archive_cache.objectstore.retry_backoff = 1
294
295 ; how many tries do do a retry fetch from this backend
296 archive_cache.objectstore.retry_attempts = 10
297
298 ; Default is $cache_dir/archive_cache if not set
256 ; Generated repo archives will be cached at this location
299 ; Generated repo archives will be cached at this location
257 ; and served from the cache during subsequent requests for the same archive of
300 ; and served from the cache during subsequent requests for the same archive of
258 ; the repository. This path is important to be shared across filesystems and with
301 ; the repository. This path is important to be shared across filesystems and with
259 ; RhodeCode and vcsserver
302 ; RhodeCode and vcsserver
260
303 archive_cache.filesystem.store_dir = /var/opt/rhodecode_data/archive_cache
261 ; Default is $cache_dir/archive_cache if not set
262 archive_cache.store_dir = %(here)s/data/archive_cache
263
304
264 ; The limit in GB sets how much data we cache before recycling last used, defaults to 10 gb
305 ; The limit in GB sets how much data we cache before recycling last used, defaults to 10 gb
265 archive_cache.cache_size_gb = 40
306 archive_cache.filesystem.cache_size_gb = 40
307
308 ; Eviction policy used to clear out after cache_size_gb limit is reached
309 archive_cache.filesystem.eviction_policy = least-recently-stored
266
310
267 ; By default cache uses sharding technique, this specifies how many shards are there
311 ; By default cache uses sharding technique, this specifies how many shards are there
268 archive_cache.cache_shards = 4
312 ; default is 8 shards
313 archive_cache.filesystem.cache_shards = 8
314
315 ; if true, this cache will try to retry with retry_attempts=N times waiting retry_backoff time
316 archive_cache.filesystem.retry = false
317
318 ; number of seconds to wait for next try using retry
319 archive_cache.filesystem.retry_backoff = 1
320
321 ; how many tries do do a retry fetch from this backend
322 archive_cache.filesystem.retry_attempts = 10
323
269
324
270 ; #############
325 ; #############
271 ; CELERY CONFIG
326 ; CELERY CONFIG
272 ; #############
327 ; #############
273
328
274 ; manually run celery: /path/to/celery worker --task-events --beat --app rhodecode.lib.celerylib.loader --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler --loglevel DEBUG --ini /path/to/rhodecode.ini
329 ; manually run celery: /path/to/celery worker --task-events --beat --app rhodecode.lib.celerylib.loader --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler --loglevel DEBUG --ini /path/to/rhodecode.ini
275
330
276 use_celery = false
331 use_celery = true
277
332
278 ; path to store schedule database
333 ; path to store schedule database
279 #celerybeat-schedule.path =
334 #celerybeat-schedule.path =
280
335
281 ; connection url to the message broker (default redis)
336 ; connection url to the message broker (default redis)
282 celery.broker_url = redis://redis:6379/8
337 celery.broker_url = redis://redis:6379/8
283
338
284 ; results backend to get results for (default redis)
339 ; results backend to get results for (default redis)
285 celery.result_backend = redis://redis:6379/8
340 celery.result_backend = redis://redis:6379/8
286
341
287 ; rabbitmq example
342 ; rabbitmq example
288 #celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
343 #celery.broker_url = amqp://rabbitmq:qweqwe@localhost:5672/rabbitmqhost
289
344
290 ; maximum tasks to execute before worker restart
345 ; maximum tasks to execute before worker restart
291 celery.max_tasks_per_child = 20
346 celery.max_tasks_per_child = 20
292
347
293 ; tasks will never be sent to the queue, but executed locally instead.
348 ; tasks will never be sent to the queue, but executed locally instead.
294 celery.task_always_eager = false
349 celery.task_always_eager = false
295
350
296 ; #############
351 ; #############
297 ; DOGPILE CACHE
352 ; DOGPILE CACHE
298 ; #############
353 ; #############
299
354
300 ; Default cache dir for caches. Putting this into a ramdisk can boost performance.
355 ; Default cache dir for caches. Putting this into a ramdisk can boost performance.
301 ; eg. /tmpfs/data_ramdisk, however this directory might require large amount of space
356 ; eg. /tmpfs/data_ramdisk, however this directory might require large amount of space
302 cache_dir = %(here)s/data
357 cache_dir = /var/opt/rhodecode_data
303
358
304 ; *********************************************
359 ; *********************************************
305 ; `sql_cache_short` cache for heavy SQL queries
360 ; `sql_cache_short` cache for heavy SQL queries
306 ; Only supported backend is `memory_lru`
361 ; Only supported backend is `memory_lru`
307 ; *********************************************
362 ; *********************************************
308 rc_cache.sql_cache_short.backend = dogpile.cache.rc.memory_lru
363 rc_cache.sql_cache_short.backend = dogpile.cache.rc.memory_lru
309 rc_cache.sql_cache_short.expiration_time = 30
364 rc_cache.sql_cache_short.expiration_time = 30
310
365
311
366
312 ; *****************************************************
367 ; *****************************************************
313 ; `cache_repo_longterm` cache for repo object instances
368 ; `cache_repo_longterm` cache for repo object instances
314 ; Only supported backend is `memory_lru`
369 ; Only supported backend is `memory_lru`
315 ; *****************************************************
370 ; *****************************************************
316 rc_cache.cache_repo_longterm.backend = dogpile.cache.rc.memory_lru
371 rc_cache.cache_repo_longterm.backend = dogpile.cache.rc.memory_lru
317 ; by default we use 30 Days, cache is still invalidated on push
372 ; by default we use 30 Days, cache is still invalidated on push
318 rc_cache.cache_repo_longterm.expiration_time = 2592000
373 rc_cache.cache_repo_longterm.expiration_time = 2592000
319 ; max items in LRU cache, set to smaller number to save memory, and expire last used caches
374 ; max items in LRU cache, set to smaller number to save memory, and expire last used caches
320 rc_cache.cache_repo_longterm.max_size = 10000
375 rc_cache.cache_repo_longterm.max_size = 10000
321
376
322
377
323 ; *********************************************
378 ; *********************************************
324 ; `cache_general` cache for general purpose use
379 ; `cache_general` cache for general purpose use
325 ; for simplicity use rc.file_namespace backend,
380 ; for simplicity use rc.file_namespace backend,
326 ; for performance and scale use rc.redis
381 ; for performance and scale use rc.redis
327 ; *********************************************
382 ; *********************************************
328 rc_cache.cache_general.backend = dogpile.cache.rc.file_namespace
383 rc_cache.cache_general.backend = dogpile.cache.rc.file_namespace
329 rc_cache.cache_general.expiration_time = 43200
384 rc_cache.cache_general.expiration_time = 43200
330 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
385 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
331 #rc_cache.cache_general.arguments.filename = /tmp/cache_general_db
386 #rc_cache.cache_general.arguments.filename = /tmp/cache_general_db
332
387
333 ; alternative `cache_general` redis backend with distributed lock
388 ; alternative `cache_general` redis backend with distributed lock
334 #rc_cache.cache_general.backend = dogpile.cache.rc.redis
389 #rc_cache.cache_general.backend = dogpile.cache.rc.redis
335 #rc_cache.cache_general.expiration_time = 300
390 #rc_cache.cache_general.expiration_time = 300
336
391
337 ; redis_expiration_time needs to be greater then expiration_time
392 ; redis_expiration_time needs to be greater then expiration_time
338 #rc_cache.cache_general.arguments.redis_expiration_time = 7200
393 #rc_cache.cache_general.arguments.redis_expiration_time = 7200
339
394
340 #rc_cache.cache_general.arguments.host = localhost
395 #rc_cache.cache_general.arguments.host = localhost
341 #rc_cache.cache_general.arguments.port = 6379
396 #rc_cache.cache_general.arguments.port = 6379
342 #rc_cache.cache_general.arguments.db = 0
397 #rc_cache.cache_general.arguments.db = 0
343 #rc_cache.cache_general.arguments.socket_timeout = 30
398 #rc_cache.cache_general.arguments.socket_timeout = 30
344 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
399 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
345 #rc_cache.cache_general.arguments.distributed_lock = true
400 #rc_cache.cache_general.arguments.distributed_lock = true
346
401
347 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
402 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
348 #rc_cache.cache_general.arguments.lock_auto_renewal = true
403 #rc_cache.cache_general.arguments.lock_auto_renewal = true
349
404
350 ; *************************************************
405 ; *************************************************
351 ; `cache_perms` cache for permission tree, auth TTL
406 ; `cache_perms` cache for permission tree, auth TTL
352 ; for simplicity use rc.file_namespace backend,
407 ; for simplicity use rc.file_namespace backend,
353 ; for performance and scale use rc.redis
408 ; for performance and scale use rc.redis
354 ; *************************************************
409 ; *************************************************
355 rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace
410 rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace
356 rc_cache.cache_perms.expiration_time = 3600
411 rc_cache.cache_perms.expiration_time = 3600
357 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
412 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
358 #rc_cache.cache_perms.arguments.filename = /tmp/cache_perms_db
413 #rc_cache.cache_perms.arguments.filename = /tmp/cache_perms_db
359
414
360 ; alternative `cache_perms` redis backend with distributed lock
415 ; alternative `cache_perms` redis backend with distributed lock
361 #rc_cache.cache_perms.backend = dogpile.cache.rc.redis
416 #rc_cache.cache_perms.backend = dogpile.cache.rc.redis
362 #rc_cache.cache_perms.expiration_time = 300
417 #rc_cache.cache_perms.expiration_time = 300
363
418
364 ; redis_expiration_time needs to be greater then expiration_time
419 ; redis_expiration_time needs to be greater then expiration_time
365 #rc_cache.cache_perms.arguments.redis_expiration_time = 7200
420 #rc_cache.cache_perms.arguments.redis_expiration_time = 7200
366
421
367 #rc_cache.cache_perms.arguments.host = localhost
422 #rc_cache.cache_perms.arguments.host = localhost
368 #rc_cache.cache_perms.arguments.port = 6379
423 #rc_cache.cache_perms.arguments.port = 6379
369 #rc_cache.cache_perms.arguments.db = 0
424 #rc_cache.cache_perms.arguments.db = 0
370 #rc_cache.cache_perms.arguments.socket_timeout = 30
425 #rc_cache.cache_perms.arguments.socket_timeout = 30
371 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
426 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
372 #rc_cache.cache_perms.arguments.distributed_lock = true
427 #rc_cache.cache_perms.arguments.distributed_lock = true
373
428
374 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
429 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
375 #rc_cache.cache_perms.arguments.lock_auto_renewal = true
430 #rc_cache.cache_perms.arguments.lock_auto_renewal = true
376
431
377 ; ***************************************************
432 ; ***************************************************
378 ; `cache_repo` cache for file tree, Readme, RSS FEEDS
433 ; `cache_repo` cache for file tree, Readme, RSS FEEDS
379 ; for simplicity use rc.file_namespace backend,
434 ; for simplicity use rc.file_namespace backend,
380 ; for performance and scale use rc.redis
435 ; for performance and scale use rc.redis
381 ; ***************************************************
436 ; ***************************************************
382 rc_cache.cache_repo.backend = dogpile.cache.rc.file_namespace
437 rc_cache.cache_repo.backend = dogpile.cache.rc.file_namespace
383 rc_cache.cache_repo.expiration_time = 2592000
438 rc_cache.cache_repo.expiration_time = 2592000
384 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
439 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
385 #rc_cache.cache_repo.arguments.filename = /tmp/cache_repo_db
440 #rc_cache.cache_repo.arguments.filename = /tmp/cache_repo_db
386
441
387 ; alternative `cache_repo` redis backend with distributed lock
442 ; alternative `cache_repo` redis backend with distributed lock
388 #rc_cache.cache_repo.backend = dogpile.cache.rc.redis
443 #rc_cache.cache_repo.backend = dogpile.cache.rc.redis
389 #rc_cache.cache_repo.expiration_time = 2592000
444 #rc_cache.cache_repo.expiration_time = 2592000
390
445
391 ; redis_expiration_time needs to be greater then expiration_time
446 ; redis_expiration_time needs to be greater then expiration_time
392 #rc_cache.cache_repo.arguments.redis_expiration_time = 2678400
447 #rc_cache.cache_repo.arguments.redis_expiration_time = 2678400
393
448
394 #rc_cache.cache_repo.arguments.host = localhost
449 #rc_cache.cache_repo.arguments.host = localhost
395 #rc_cache.cache_repo.arguments.port = 6379
450 #rc_cache.cache_repo.arguments.port = 6379
396 #rc_cache.cache_repo.arguments.db = 1
451 #rc_cache.cache_repo.arguments.db = 1
397 #rc_cache.cache_repo.arguments.socket_timeout = 30
452 #rc_cache.cache_repo.arguments.socket_timeout = 30
398 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
453 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
399 #rc_cache.cache_repo.arguments.distributed_lock = true
454 #rc_cache.cache_repo.arguments.distributed_lock = true
400
455
401 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
456 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
402 #rc_cache.cache_repo.arguments.lock_auto_renewal = true
457 #rc_cache.cache_repo.arguments.lock_auto_renewal = true
403
458
404 ; ##############
459 ; ##############
405 ; BEAKER SESSION
460 ; BEAKER SESSION
406 ; ##############
461 ; ##############
407
462
408 ; beaker.session.type is type of storage options for the logged users sessions. Current allowed
463 ; beaker.session.type is type of storage options for the logged users sessions. Current allowed
409 ; types are file, ext:redis, ext:database, ext:memcached
464 ; types are file, ext:redis, ext:database, ext:memcached
410 ; Fastest ones are ext:redis and ext:database, DO NOT use memory type for session
465 ; Fastest ones are ext:redis and ext:database, DO NOT use memory type for session
411 beaker.session.type = file
466 #beaker.session.type = file
412 beaker.session.data_dir = %(here)s/data/sessions
467 #beaker.session.data_dir = %(here)s/data/sessions
413
468
414 ; Redis based sessions
469 ; Redis based sessions
415 #beaker.session.type = ext:redis
470 beaker.session.type = ext:redis
416 #beaker.session.url = redis://127.0.0.1:6379/2
471 beaker.session.url = redis://redis:6379/2
417
472
418 ; DB based session, fast, and allows easy management over logged in users
473 ; DB based session, fast, and allows easy management over logged in users
419 #beaker.session.type = ext:database
474 #beaker.session.type = ext:database
420 #beaker.session.table_name = db_session
475 #beaker.session.table_name = db_session
421 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
476 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
422 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
477 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
423 #beaker.session.sa.pool_recycle = 3600
478 #beaker.session.sa.pool_recycle = 3600
424 #beaker.session.sa.echo = false
479 #beaker.session.sa.echo = false
425
480
426 beaker.session.key = rhodecode
481 beaker.session.key = rhodecode
427 beaker.session.secret = production-rc-uytcxaz
482 beaker.session.secret = production-rc-uytcxaz
428 beaker.session.lock_dir = %(here)s/data/sessions/lock
483 beaker.session.lock_dir = /data_ramdisk/lock
429
484
430 ; Secure encrypted cookie. Requires AES and AES python libraries
485 ; Secure encrypted cookie. Requires AES and AES python libraries
431 ; you must disable beaker.session.secret to use this
486 ; you must disable beaker.session.secret to use this
432 #beaker.session.encrypt_key = key_for_encryption
487 #beaker.session.encrypt_key = key_for_encryption
433 #beaker.session.validate_key = validation_key
488 #beaker.session.validate_key = validation_key
434
489
435 ; Sets session as invalid (also logging out user) if it haven not been
490 ; Sets session as invalid (also logging out user) if it haven not been
436 ; accessed for given amount of time in seconds
491 ; accessed for given amount of time in seconds
437 beaker.session.timeout = 2592000
492 beaker.session.timeout = 2592000
438 beaker.session.httponly = true
493 beaker.session.httponly = true
439
494
440 ; Path to use for the cookie. Set to prefix if you use prefix middleware
495 ; Path to use for the cookie. Set to prefix if you use prefix middleware
441 #beaker.session.cookie_path = /custom_prefix
496 #beaker.session.cookie_path = /custom_prefix
442
497
443 ; Set https secure cookie
498 ; Set https secure cookie
444 beaker.session.secure = false
499 beaker.session.secure = false
445
500
446 ; default cookie expiration time in seconds, set to `true` to set expire
501 ; default cookie expiration time in seconds, set to `true` to set expire
447 ; at browser close
502 ; at browser close
448 #beaker.session.cookie_expires = 3600
503 #beaker.session.cookie_expires = 3600
449
504
450 ; #############################
505 ; #############################
451 ; SEARCH INDEXING CONFIGURATION
506 ; SEARCH INDEXING CONFIGURATION
452 ; #############################
507 ; #############################
453
508
454 ; Full text search indexer is available in rhodecode-tools under
509 ; Full text search indexer is available in rhodecode-tools under
455 ; `rhodecode-tools index` command
510 ; `rhodecode-tools index` command
456
511
457 ; WHOOSH Backend, doesn't require additional services to run
512 ; WHOOSH Backend, doesn't require additional services to run
458 ; it works good with few dozen repos
513 ; it works good with few dozen repos
459 search.module = rhodecode.lib.index.whoosh
514 search.module = rhodecode.lib.index.whoosh
460 search.location = %(here)s/data/index
515 search.location = %(here)s/data/index
461
516
462 ; ####################
517 ; ####################
463 ; CHANNELSTREAM CONFIG
518 ; CHANNELSTREAM CONFIG
464 ; ####################
519 ; ####################
465
520
466 ; channelstream enables persistent connections and live notification
521 ; channelstream enables persistent connections and live notification
467 ; in the system. It's also used by the chat system
522 ; in the system. It's also used by the chat system
468
523
469 channelstream.enabled = false
524 channelstream.enabled = true
470
525
471 ; server address for channelstream server on the backend
526 ; server address for channelstream server on the backend
472 channelstream.server = 127.0.0.1:9800
527 channelstream.server = channelstream:9800
473
528
474 ; location of the channelstream server from outside world
529 ; location of the channelstream server from outside world
475 ; use ws:// for http or wss:// for https. This address needs to be handled
530 ; use ws:// for http or wss:// for https. This address needs to be handled
476 ; by external HTTP server such as Nginx or Apache
531 ; by external HTTP server such as Nginx or Apache
477 ; see Nginx/Apache configuration examples in our docs
532 ; see Nginx/Apache configuration examples in our docs
478 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
533 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
479 channelstream.secret = secret
534 channelstream.secret = ENV_GENERATED
480 channelstream.history.location = %(here)s/channelstream_history
535 channelstream.history.location = /var/opt/rhodecode_data/channelstream_history
481
536
482 ; Internal application path that Javascript uses to connect into.
537 ; Internal application path that Javascript uses to connect into.
483 ; If you use proxy-prefix the prefix should be added before /_channelstream
538 ; If you use proxy-prefix the prefix should be added before /_channelstream
484 channelstream.proxy_path = /_channelstream
539 channelstream.proxy_path = /_channelstream
485
540
486
541
487 ; ##############################
542 ; ##############################
488 ; MAIN RHODECODE DATABASE CONFIG
543 ; MAIN RHODECODE DATABASE CONFIG
489 ; ##############################
544 ; ##############################
490
545
491 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
546 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
492 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
547 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
493 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode?charset=utf8
548 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode?charset=utf8
494 ; pymysql is an alternative driver for MySQL, use in case of problems with default one
549 ; pymysql is an alternative driver for MySQL, use in case of problems with default one
495 #sqlalchemy.db1.url = mysql+pymysql://root:qweqwe@localhost/rhodecode
550 #sqlalchemy.db1.url = mysql+pymysql://root:qweqwe@localhost/rhodecode
496
551
497 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
552 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
498
553
499 ; see sqlalchemy docs for other advanced settings
554 ; see sqlalchemy docs for other advanced settings
500 ; print the sql statements to output
555 ; print the sql statements to output
501 sqlalchemy.db1.echo = false
556 sqlalchemy.db1.echo = false
502
557
503 ; recycle the connections after this amount of seconds
558 ; recycle the connections after this amount of seconds
504 sqlalchemy.db1.pool_recycle = 3600
559 sqlalchemy.db1.pool_recycle = 3600
505
560
506 ; the number of connections to keep open inside the connection pool.
561 ; the number of connections to keep open inside the connection pool.
507 ; 0 indicates no limit
562 ; 0 indicates no limit
508 ; the general calculus with gevent is:
563 ; the general calculus with gevent is:
509 ; if your system allows 500 concurrent greenlets (max_connections) that all do database access,
564 ; if your system allows 500 concurrent greenlets (max_connections) that all do database access,
510 ; then increase pool size + max overflow so that they add up to 500.
565 ; then increase pool size + max overflow so that they add up to 500.
511 #sqlalchemy.db1.pool_size = 5
566 #sqlalchemy.db1.pool_size = 5
512
567
513 ; The number of connections to allow in connection pool "overflow", that is
568 ; The number of connections to allow in connection pool "overflow", that is
514 ; connections that can be opened above and beyond the pool_size setting,
569 ; connections that can be opened above and beyond the pool_size setting,
515 ; which defaults to five.
570 ; which defaults to five.
516 #sqlalchemy.db1.max_overflow = 10
571 #sqlalchemy.db1.max_overflow = 10
517
572
518 ; Connection check ping, used to detect broken database connections
573 ; Connection check ping, used to detect broken database connections
519 ; could be enabled to better handle cases if MySQL has gone away errors
574 ; could be enabled to better handle cases if MySQL has gone away errors
520 #sqlalchemy.db1.ping_connection = true
575 #sqlalchemy.db1.ping_connection = true
521
576
522 ; ##########
577 ; ##########
523 ; VCS CONFIG
578 ; VCS CONFIG
524 ; ##########
579 ; ##########
525 vcs.server.enable = true
580 vcs.server.enable = true
526 vcs.server = localhost:9900
581 vcs.server = vcsserver:10010
527
582
528 ; Web server connectivity protocol, responsible for web based VCS operations
583 ; Web server connectivity protocol, responsible for web based VCS operations
529 ; Available protocols are:
584 ; Available protocols are:
530 ; `http` - use http-rpc backend (default)
585 ; `http` - use http-rpc backend (default)
531 vcs.server.protocol = http
586 vcs.server.protocol = http
532
587
533 ; Push/Pull operations protocol, available options are:
588 ; Push/Pull operations protocol, available options are:
534 ; `http` - use http-rpc backend (default)
589 ; `http` - use http-rpc backend (default)
535 vcs.scm_app_implementation = http
590 vcs.scm_app_implementation = http
536
591
537 ; Push/Pull operations hooks protocol, available options are:
592 ; Push/Pull operations hooks protocol, available options are:
538 ; `http` - use http-rpc backend (default)
593 ; `http` - use http-rpc backend (default)
594 ; `celery` - use celery based hooks
539 vcs.hooks.protocol = http
595 vcs.hooks.protocol = http
540
596
541 ; Host on which this instance is listening for hooks. vcsserver will call this host to pull/push hooks so it should be
597 ; Host on which this instance is listening for hooks. vcsserver will call this host to pull/push hooks so it should be
542 ; accessible via network.
598 ; accessible via network.
543 ; Use vcs.hooks.host = "*" to bind to current hostname (for Docker)
599 ; Use vcs.hooks.host = "*" to bind to current hostname (for Docker)
544 vcs.hooks.host = *
600 vcs.hooks.host = *
545
601
546 ; Start VCSServer with this instance as a subprocess, useful for development
602 ; Start VCSServer with this instance as a subprocess, useful for development
547 vcs.start_server = false
603 vcs.start_server = false
548
604
549 ; List of enabled VCS backends, available options are:
605 ; List of enabled VCS backends, available options are:
550 ; `hg` - mercurial
606 ; `hg` - mercurial
551 ; `git` - git
607 ; `git` - git
552 ; `svn` - subversion
608 ; `svn` - subversion
553 vcs.backends = hg, git, svn
609 vcs.backends = hg, git, svn
554
610
555 ; Wait this number of seconds before killing connection to the vcsserver
611 ; Wait this number of seconds before killing connection to the vcsserver
556 vcs.connection_timeout = 3600
612 vcs.connection_timeout = 3600
557
613
558 ; Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
559 ; Set a numeric version for your current SVN e.g 1.8, or 1.12
560 ; Legacy available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
561 #vcs.svn.compatible_version = 1.8
562
563 ; Cache flag to cache vcsserver remote calls locally
614 ; Cache flag to cache vcsserver remote calls locally
564 ; It uses cache_region `cache_repo`
615 ; It uses cache_region `cache_repo`
565 vcs.methods.cache = true
616 vcs.methods.cache = true
566
617
567 ; ####################################################
618 ; ####################################################
568 ; Subversion proxy support (mod_dav_svn)
619 ; Subversion proxy support (mod_dav_svn)
569 ; Maps RhodeCode repo groups into SVN paths for Apache
620 ; Maps RhodeCode repo groups into SVN paths for Apache
570 ; ####################################################
621 ; ####################################################
571
622
623 ; Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
624 ; Set a numeric version for your current SVN e.g 1.8, or 1.12
625 ; Legacy available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible, pre-1.9-compatible
626 #vcs.svn.compatible_version = 1.8
627
628 ; Redis connection settings for svn integrations logic
629 ; This connection string needs to be the same on ce and vcsserver
630 vcs.svn.redis_conn = redis://redis:6379/0
631
632 ; Enable SVN proxy of requests over HTTP
633 vcs.svn.proxy.enabled = true
634
635 ; host to connect to running SVN subsystem
636 vcs.svn.proxy.host = http://svn:8090
637
572 ; Enable or disable the config file generation.
638 ; Enable or disable the config file generation.
573 svn.proxy.generate_config = false
639 svn.proxy.generate_config = true
574
640
575 ; Generate config file with `SVNListParentPath` set to `On`.
641 ; Generate config file with `SVNListParentPath` set to `On`.
576 svn.proxy.list_parent_path = true
642 svn.proxy.list_parent_path = true
577
643
578 ; Set location and file name of generated config file.
644 ; Set location and file name of generated config file.
579 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
645 svn.proxy.config_file_path = /etc/rhodecode/conf/svn/mod_dav_svn.conf
580
646
581 ; alternative mod_dav config template. This needs to be a valid mako template
647 ; alternative mod_dav config template. This needs to be a valid mako template
582 ; Example template can be found in the source code:
648 ; Example template can be found in the source code:
583 ; rhodecode/apps/svn_support/templates/mod-dav-svn.conf.mako
649 ; rhodecode/apps/svn_support/templates/mod-dav-svn.conf.mako
584 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
650 #svn.proxy.config_template = ~/.rccontrol/enterprise-1/custom_svn_conf.mako
585
651
586 ; Used as a prefix to the `Location` block in the generated config file.
652 ; Used as a prefix to the `Location` block in the generated config file.
587 ; In most cases it should be set to `/`.
653 ; In most cases it should be set to `/`.
588 svn.proxy.location_root = /
654 svn.proxy.location_root = /
589
655
590 ; Command to reload the mod dav svn configuration on change.
656 ; Command to reload the mod dav svn configuration on change.
591 ; Example: `/etc/init.d/apache2 reload` or /home/USER/apache_reload.sh
657 ; Example: `/etc/init.d/apache2 reload` or /home/USER/apache_reload.sh
592 ; Make sure user who runs RhodeCode process is allowed to reload Apache
658 ; Make sure user who runs RhodeCode process is allowed to reload Apache
593 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
659 #svn.proxy.reload_cmd = /etc/init.d/apache2 reload
594
660
595 ; If the timeout expires before the reload command finishes, the command will
661 ; If the timeout expires before the reload command finishes, the command will
596 ; be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
662 ; be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
597 #svn.proxy.reload_timeout = 10
663 #svn.proxy.reload_timeout = 10
598
664
599 ; ####################
665 ; ####################
600 ; SSH Support Settings
666 ; SSH Support Settings
601 ; ####################
667 ; ####################
602
668
603 ; Defines if a custom authorized_keys file should be created and written on
669 ; Defines if a custom authorized_keys file should be created and written on
604 ; any change user ssh keys. Setting this to false also disables possibility
670 ; any change user ssh keys. Setting this to false also disables possibility
605 ; of adding SSH keys by users from web interface. Super admins can still
671 ; of adding SSH keys by users from web interface. Super admins can still
606 ; manage SSH Keys.
672 ; manage SSH Keys.
607 ssh.generate_authorized_keyfile = false
673 ssh.generate_authorized_keyfile = true
608
674
609 ; Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
675 ; Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
610 # ssh.authorized_keys_ssh_opts =
676 # ssh.authorized_keys_ssh_opts =
611
677
612 ; Path to the authorized_keys file where the generate entries are placed.
678 ; Path to the authorized_keys file where the generate entries are placed.
613 ; It is possible to have multiple key files specified in `sshd_config` e.g.
679 ; It is possible to have multiple key files specified in `sshd_config` e.g.
614 ; AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
680 ; AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
615 ssh.authorized_keys_file_path = ~/.ssh/authorized_keys_rhodecode
681 ssh.authorized_keys_file_path = /etc/rhodecode/conf/ssh/authorized_keys_rhodecode
616
682
617 ; Command to execute the SSH wrapper. The binary is available in the
683 ; Command to execute the SSH wrapper. The binary is available in the
618 ; RhodeCode installation directory.
684 ; RhodeCode installation directory.
619 ; e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
685 ; legacy: /usr/local/bin/rhodecode_bin/bin/rc-ssh-wrapper
620 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
686 ; new rewrite: /usr/local/bin/rhodecode_bin/bin/rc-ssh-wrapper-v2
687 ssh.wrapper_cmd = /usr/local/bin/rhodecode_bin/bin/rc-ssh-wrapper
621
688
622 ; Allow shell when executing the ssh-wrapper command
689 ; Allow shell when executing the ssh-wrapper command
623 ssh.wrapper_cmd_allow_shell = false
690 ssh.wrapper_cmd_allow_shell = false
624
691
625 ; Enables logging, and detailed output send back to the client during SSH
692 ; Enables logging, and detailed output send back to the client during SSH
626 ; operations. Useful for debugging, shouldn't be used in production.
693 ; operations. Useful for debugging, shouldn't be used in production.
627 ssh.enable_debug_logging = false
694 ssh.enable_debug_logging = false
628
695
629 ; Paths to binary executable, by default they are the names, but we can
696 ; Paths to binary executable, by default they are the names, but we can
630 ; override them if we want to use a custom one
697 ; override them if we want to use a custom one
631 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
698 ssh.executable.hg = /usr/local/bin/rhodecode_bin/vcs_bin/hg
632 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
699 ssh.executable.git = /usr/local/bin/rhodecode_bin/vcs_bin/git
633 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
700 ssh.executable.svn = /usr/local/bin/rhodecode_bin/vcs_bin/svnserve
634
701
635 ; Enables SSH key generator web interface. Disabling this still allows users
702 ; Enables SSH key generator web interface. Disabling this still allows users
636 ; to add their own keys.
703 ; to add their own keys.
637 ssh.enable_ui_key_generator = true
704 ssh.enable_ui_key_generator = true
638
705
639
640 ; #################
641 ; APPENLIGHT CONFIG
642 ; #################
643
644 ; Appenlight is tailored to work with RhodeCode, see
645 ; http://appenlight.rhodecode.com for details how to obtain an account
646
647 ; Appenlight integration enabled
648 #appenlight = false
649
650 #appenlight.server_url = https://api.appenlight.com
651 #appenlight.api_key = YOUR_API_KEY
652 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
653
654 ; used for JS client
655 #appenlight.api_public_key = YOUR_API_PUBLIC_KEY
656
657 ; TWEAK AMOUNT OF INFO SENT HERE
658
659 ; enables 404 error logging (default False)
660 #appenlight.report_404 = false
661
662 ; time in seconds after request is considered being slow (default 1)
663 #appenlight.slow_request_time = 1
664
665 ; record slow requests in application
666 ; (needs to be enabled for slow datastore recording and time tracking)
667 #appenlight.slow_requests = true
668
669 ; enable hooking to application loggers
670 #appenlight.logging = true
671
672 ; minimum log level for log capture
673 #ppenlight.logging.level = WARNING
674
675 ; send logs only from erroneous/slow requests
676 ; (saves API quota for intensive logging)
677 #appenlight.logging_on_error = false
678
679 ; list of additional keywords that should be grabbed from environ object
680 ; can be string with comma separated list of words in lowercase
681 ; (by default client will always send following info:
682 ; 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
683 ; start with HTTP* this list be extended with additional keywords here
684 #appenlight.environ_keys_whitelist =
685
686 ; list of keywords that should be blanked from request object
687 ; can be string with comma separated list of words in lowercase
688 ; (by default client will always blank keys that contain following words
689 ; 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
690 ; this list be extended with additional keywords set here
691 #appenlight.request_keys_blacklist =
692
693 ; list of namespaces that should be ignores when gathering log entries
694 ; can be string with comma separated list of namespaces
695 ; (by default the client ignores own entries: appenlight_client.client)
696 #appenlight.log_namespace_blacklist =
697
698 ; Statsd client config, this is used to send metrics to statsd
706 ; Statsd client config, this is used to send metrics to statsd
699 ; We recommend setting statsd_exported and scrape them using Prometheus
707 ; We recommend setting statsd_exported and scrape them using Prometheus
700 #statsd.enabled = false
708 #statsd.enabled = false
701 #statsd.statsd_host = 0.0.0.0
709 #statsd.statsd_host = 0.0.0.0
702 #statsd.statsd_port = 8125
710 #statsd.statsd_port = 8125
703 #statsd.statsd_prefix =
711 #statsd.statsd_prefix =
704 #statsd.statsd_ipv6 = false
712 #statsd.statsd_ipv6 = false
705
713
706 ; configure logging automatically at server startup set to false
714 ; configure logging automatically at server startup set to false
707 ; to use the below custom logging config.
715 ; to use the below custom logging config.
708 ; RC_LOGGING_FORMATTER
716 ; RC_LOGGING_FORMATTER
709 ; RC_LOGGING_LEVEL
717 ; RC_LOGGING_LEVEL
710 ; env variables can control the settings for logging in case of autoconfigure
718 ; env variables can control the settings for logging in case of autoconfigure
711
719
712 #logging.autoconfigure = true
720 #logging.autoconfigure = true
713
721
714 ; specify your own custom logging config file to configure logging
722 ; specify your own custom logging config file to configure logging
715 #logging.logging_conf_file = /path/to/custom_logging.ini
723 #logging.logging_conf_file = /path/to/custom_logging.ini
716
724
717 ; Dummy marker to add new entries after.
725 ; Dummy marker to add new entries after.
718 ; Add any custom entries below. Please don't remove this marker.
726 ; Add any custom entries below. Please don't remove this marker.
719 custom.conf = 1
727 custom.conf = 1
720
728
721
729
722 ; #####################
730 ; #####################
723 ; LOGGING CONFIGURATION
731 ; LOGGING CONFIGURATION
724 ; #####################
732 ; #####################
725
733
726 [loggers]
734 [loggers]
727 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
735 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
728
736
729 [handlers]
737 [handlers]
730 keys = console, console_sql
738 keys = console, console_sql
731
739
732 [formatters]
740 [formatters]
733 keys = generic, json, color_formatter, color_formatter_sql
741 keys = generic, json, color_formatter, color_formatter_sql
734
742
735 ; #######
743 ; #######
736 ; LOGGERS
744 ; LOGGERS
737 ; #######
745 ; #######
738 [logger_root]
746 [logger_root]
739 level = NOTSET
747 level = NOTSET
740 handlers = console
748 handlers = console
741
749
742 [logger_sqlalchemy]
750 [logger_sqlalchemy]
743 level = INFO
751 level = INFO
744 handlers = console_sql
752 handlers = console_sql
745 qualname = sqlalchemy.engine
753 qualname = sqlalchemy.engine
746 propagate = 0
754 propagate = 0
747
755
748 [logger_beaker]
756 [logger_beaker]
749 level = DEBUG
757 level = DEBUG
750 handlers =
758 handlers =
751 qualname = beaker.container
759 qualname = beaker.container
752 propagate = 1
760 propagate = 1
753
761
754 [logger_rhodecode]
762 [logger_rhodecode]
755 level = DEBUG
763 level = DEBUG
756 handlers =
764 handlers =
757 qualname = rhodecode
765 qualname = rhodecode
758 propagate = 1
766 propagate = 1
759
767
760 [logger_ssh_wrapper]
768 [logger_ssh_wrapper]
761 level = DEBUG
769 level = DEBUG
762 handlers =
770 handlers =
763 qualname = ssh_wrapper
771 qualname = ssh_wrapper
764 propagate = 1
772 propagate = 1
765
773
766 [logger_celery]
774 [logger_celery]
767 level = DEBUG
775 level = DEBUG
768 handlers =
776 handlers =
769 qualname = celery
777 qualname = celery
770
778
771
779
772 ; ########
780 ; ########
773 ; HANDLERS
781 ; HANDLERS
774 ; ########
782 ; ########
775
783
776 [handler_console]
784 [handler_console]
777 class = StreamHandler
785 class = StreamHandler
778 args = (sys.stderr, )
786 args = (sys.stderr, )
779 level = INFO
787 level = INFO
780 ; To enable JSON formatted logs replace 'generic/color_formatter' with 'json'
788 ; To enable JSON formatted logs replace 'generic/color_formatter' with 'json'
781 ; This allows sending properly formatted logs to grafana loki or elasticsearch
789 ; This allows sending properly formatted logs to grafana loki or elasticsearch
782 formatter = generic
790 formatter = generic
783
791
784 [handler_console_sql]
792 [handler_console_sql]
785 ; "level = DEBUG" logs SQL queries and results.
793 ; "level = DEBUG" logs SQL queries and results.
786 ; "level = INFO" logs SQL queries.
794 ; "level = INFO" logs SQL queries.
787 ; "level = WARN" logs neither. (Recommended for production systems.)
795 ; "level = WARN" logs neither. (Recommended for production systems.)
788 class = StreamHandler
796 class = StreamHandler
789 args = (sys.stderr, )
797 args = (sys.stderr, )
790 level = WARN
798 level = WARN
791 ; To enable JSON formatted logs replace 'generic/color_formatter_sql' with 'json'
799 ; To enable JSON formatted logs replace 'generic/color_formatter_sql' with 'json'
792 ; This allows sending properly formatted logs to grafana loki or elasticsearch
800 ; This allows sending properly formatted logs to grafana loki or elasticsearch
793 formatter = generic
801 formatter = generic
794
802
795 ; ##########
803 ; ##########
796 ; FORMATTERS
804 ; FORMATTERS
797 ; ##########
805 ; ##########
798
806
799 [formatter_generic]
807 [formatter_generic]
800 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
808 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
801 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
809 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
802 datefmt = %Y-%m-%d %H:%M:%S
810 datefmt = %Y-%m-%d %H:%M:%S
803
811
804 [formatter_color_formatter]
812 [formatter_color_formatter]
805 class = rhodecode.lib.logging_formatter.ColorFormatter
813 class = rhodecode.lib.logging_formatter.ColorFormatter
806 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
814 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
807 datefmt = %Y-%m-%d %H:%M:%S
815 datefmt = %Y-%m-%d %H:%M:%S
808
816
809 [formatter_color_formatter_sql]
817 [formatter_color_formatter_sql]
810 class = rhodecode.lib.logging_formatter.ColorFormatterSql
818 class = rhodecode.lib.logging_formatter.ColorFormatterSql
811 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
819 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
812 datefmt = %Y-%m-%d %H:%M:%S
820 datefmt = %Y-%m-%d %H:%M:%S
813
821
814 [formatter_json]
822 [formatter_json]
815 format = %(timestamp)s %(levelname)s %(name)s %(message)s %(req_id)s
823 format = %(timestamp)s %(levelname)s %(name)s %(message)s %(req_id)s
816 class = rhodecode.lib._vendor.jsonlogger.JsonFormatter
824 class = rhodecode.lib._vendor.jsonlogger.JsonFormatter
@@ -1,31 +1,31 b''
1 .. _lab-settings:
1 .. _lab-settings:
2
2
3 Lab Settings
3 Lab Settings
4 ============
4 ============
5
5
6 |RCE| Lab Settings is for delivering features which may require an additional
6 |RCE| Lab Settings is for delivering features which may require an additional
7 level of support to optimize for production scenarios. To enable lab settings,
7 level of support to optimize for production scenarios. To enable lab settings,
8 use the following instructions:
8 use the following instructions:
9
9
10 1. Open the |RCE| configuration file,
10 1. Open the |RCE| configuration file,
11 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
11 :file:`config/_shared/rhodecode.ini`
12
12
13 2. Add the following configuration option in the ``[app:main]`` section.
13 2. Add the following configuration option in the ``[app:main]`` section.
14
14
15 .. code-block:: bash
15 .. code-block:: bash
16
16
17 [app:main]
17 [app:main]
18
18
19 ## Display extended labs settings
19 ## Display extended labs settings
20 labs_settings_active = true
20 labs_settings_active = true
21
21
22 3. Restart your |RCE| instance
22 3. Restart your |RCE| instance
23
23
24 .. code-block:: bash
24 .. code-block:: bash
25
25
26 $ rccontrol restart enterprise-1
26 $ rccontrol restart enterprise-1
27
27
28 4. You will see the labs setting on the
28 4. You will see the labs setting on the
29 :menuselection:`Admin --> Settings --> labs` page.
29 :menuselection:`Admin --> Settings --> labs` page.
30
30
31 .. image:: ../images/lab-setting.png
31 .. image:: ../images/lab-setting.png
@@ -1,57 +1,57 b''
1 .. _x-frame:
1 .. _x-frame:
2
2
3 Securing HTTPS Connections
3 Securing HTTPS Connections
4 --------------------------
4 --------------------------
5
5
6 * To secure your |RCE| instance against `Cross Frame Scripting`_ exploits, you
6 * To secure your |RCE| instance against `Cross Frame Scripting`_ exploits, you
7 should configure your webserver ``x-frame-options`` setting.
7 should configure your webserver ``x-frame-options`` setting.
8
8
9 * To configure your instance for `HTTP Strict Transport Security`_, you need to
9 * To configure your instance for `HTTP Strict Transport Security`_, you need to
10 configure the ``Strict-Transport-Security`` setting.
10 configure the ``Strict-Transport-Security`` setting.
11
11
12 Nginx
12 Nginx
13 ^^^^^
13 ^^^^^
14
14
15 In your nginx configuration, add the following lines in the correct files. For
15 In your nginx configuration, add the following lines in the correct files. For
16 more detailed information see the :ref:`nginx-ws-ref` section.
16 more detailed information see the :ref:`nginx-ws-ref` section.
17
17
18 .. code-block:: nginx
18 .. code-block:: nginx
19
19
20 # Add this line to the nginx.conf file
20 # Add this line to the nginx.conf file
21 add_header X-Frame-Options SAMEORIGIN;
21 add_header X-Frame-Options SAMEORIGIN;
22
22
23 # This line needs to be added inside your virtual hosts block/file
23 # This line needs to be added inside your virtual hosts block/file
24 add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
24 add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
25
25
26 Apache
26 Apache
27 ^^^^^^
27 ^^^^^^
28
28
29 In your :file:`apache2.conf` file, add the following line. For more detailed
29 In your :file:`apache2.conf` file, add the following line. For more detailed
30 information see the :ref:`apache-ws-ref` section.
30 information see the :ref:`apache-ws-ref` section.
31
31
32 .. code-block:: apache
32 .. code-block:: apache
33
33
34 # Add this to your virtual hosts file
34 # Add this to your virtual hosts file
35 Header always append X-Frame-Options SAMEORIGIN
35 Header always append X-Frame-Options SAMEORIGIN
36
36
37 # Add this line in your virtual hosts file
37 # Add this line in your virtual hosts file
38 Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
38 Header always set Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"
39
39
40 |RCE| Configuration
40 |RCE| Configuration
41 ^^^^^^^^^^^^^^^^^^^
41 ^^^^^^^^^^^^^^^^^^^
42
42
43 |RCE| can also be configured to force strict *https* connections and Strict
43 |RCE| can also be configured to force strict *https* connections and Strict
44 Transport Security. To set this, configure the following options to ``true``
44 Transport Security. To set this, configure the following options to ``true``
45 in the :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file.
45 in the :file:`config/_shared/rhodecode.ini` file.
46
46
47 .. code-block:: ini
47 .. code-block:: ini
48
48
49 ## force https in RhodeCode, fixes https redirects, assumes it's always https
49 ## force https in RhodeCode, fixes https redirects, assumes it's always https
50 force_https = false
50 force_https = false
51
51
52 ## use Strict-Transport-Security headers
52 ## use Strict-Transport-Security headers
53 use_htsts = false
53 use_htsts = false
54
54
55
55
56 .. _Cross Frame Scripting: https://www.owasp.org/index.php/Cross_Frame_Scripting
56 .. _Cross Frame Scripting: https://www.owasp.org/index.php/Cross_Frame_Scripting
57 .. _HTTP Strict Transport Security: https://www.owasp.org/index.php/HTTP_Strict_Transport_Security No newline at end of file
57 .. _HTTP Strict Transport Security: https://www.owasp.org/index.php/HTTP_Strict_Transport_Security
@@ -1,179 +1,179 b''
1 .. _sec-your-server:
1 .. _sec-your-server:
2
2
3 Securing Your Server
3 Securing Your Server
4 --------------------
4 --------------------
5
5
6 |RCE| runs on your hardware, and while it is developed with security in mind
6 |RCE| runs on your hardware, and while it is developed with security in mind
7 it is also important that you ensure your servers are well secured. In this
7 it is also important that you ensure your servers are well secured. In this
8 section we will cover some basic security practices that are best to
8 section we will cover some basic security practices that are best to
9 configure when setting up your |RCE| instances.
9 configure when setting up your |RCE| instances.
10
10
11 SSH Keys
11 SSH Keys
12 ^^^^^^^^
12 ^^^^^^^^
13
13
14 Using SSH keys to access your server provides more security than using the
14 Using SSH keys to access your server provides more security than using the
15 standard username and password combination. To set up your SSH Keys, use the
15 standard username and password combination. To set up your SSH Keys, use the
16 following steps:
16 following steps:
17
17
18 1. On your local machine create the public/private key combination. The
18 1. On your local machine create the public/private key combination. The
19 private key you will keep, and the matching public key is copied to the
19 private key you will keep, and the matching public key is copied to the
20 server. Setting a passphrase here is optional, if you set one you will
20 server. Setting a passphrase here is optional, if you set one you will
21 always be prompted for it when logging in.
21 always be prompted for it when logging in.
22
22
23 .. code-block:: bash
23 .. code-block:: bash
24
24
25 # Generate SSH Keys
25 # Generate SSH Keys
26 user@ubuntu:~$ ssh-keygen -t rsa
26 user@ubuntu:~$ ssh-keygen -t rsa
27
27
28 .. code-block:: bash
28 .. code-block:: bash
29
29
30 Generating public/private rsa key pair.
30 Generating public/private rsa key pair.
31 Enter file in which to save the key (/home/user/.ssh/id_rsa):
31 Enter file in which to save the key (/home/user/.ssh/id_rsa):
32 Created directory '/home/user/.ssh'.
32 Created directory '/home/user/.ssh'.
33 Enter passphrase (empty for no passphrase):
33 Enter passphrase (empty for no passphrase):
34 Enter same passphrase again:
34 Enter same passphrase again:
35 Your identification has been saved in /home/user/.ssh/id_rsa.
35 Your identification has been saved in /home/user/.ssh/id_rsa.
36 Your public key has been saved in /home/user/.ssh/id_rsa.pub.
36 Your public key has been saved in /home/user/.ssh/id_rsa.pub.
37 The key fingerprint is:
37 The key fingerprint is:
38 02:82:38:95:e5:30:d2:ad:17:60:15:7f:94:17:9f:30 user@ubuntu
38 02:82:38:95:e5:30:d2:ad:17:60:15:7f:94:17:9f:30 user@ubuntu
39 The key\'s randomart image is:
39 The key\'s randomart image is:
40 +--[ RSA 2048]----+
40 +--[ RSA 2048]----+
41
41
42 2. SFTP to your server, and copy the public key to the ``~/.ssh`` folder.
42 2. SFTP to your server, and copy the public key to the ``~/.ssh`` folder.
43
43
44 .. code-block:: bash
44 .. code-block:: bash
45
45
46 # SFTP to your server
46 # SFTP to your server
47 $ sftp user@hostname
47 $ sftp user@hostname
48
48
49 # copy your public key
49 # copy your public key
50 sftp> mput /home/user/.ssh/id_rsa.pub /home/user/.ssh
50 sftp> mput /home/user/.ssh/id_rsa.pub /home/user/.ssh
51 Uploading /home/user/.ssh/id_rsa.pub to /home/user/.ssh/id_rsa.pub
51 Uploading /home/user/.ssh/id_rsa.pub to /home/user/.ssh/id_rsa.pub
52 /home/user/.ssh/id_rsa.pub 100% 394 0.4KB/s 00:00
52 /home/user/.ssh/id_rsa.pub 100% 394 0.4KB/s 00:00
53
53
54 3. On your server, add the public key to the :file:`~/.ssh/authorized_keys`
54 3. On your server, add the public key to the :file:`~/.ssh/authorized_keys`
55 file.
55 file.
56
56
57 .. code-block:: bash
57 .. code-block:: bash
58
58
59 $ cat /home/user/.ssh/id_rsa.pub > /home/user/.ssh/authorized_keys
59 $ cat /home/user/.ssh/id_rsa.pub > /home/user/.ssh/authorized_keys
60
60
61 You should now be able to log into your server using your SSH
61 You should now be able to log into your server using your SSH
62 Keys. If you've added a passphrase you'll be asked for it. For more
62 Keys. If you've added a passphrase you'll be asked for it. For more
63 information about using SSH keys with |RCE| |repos|, see the
63 information about using SSH keys with |RCE| |repos|, see the
64 :ref:`ssh-connection` section.
64 :ref:`ssh-connection` section.
65
65
66 VPN Whitelist
66 VPN Whitelist
67 ^^^^^^^^^^^^^
67 ^^^^^^^^^^^^^
68
68
69 Most company networks will have a VPN. If you need to set one up, there are
69 Most company networks will have a VPN. If you need to set one up, there are
70 many tutorials online for how to do that. Getting it right requires good
70 many tutorials online for how to do that. Getting it right requires good
71 knowledge and attention to detail. Once set up, you can configure your
71 knowledge and attention to detail. Once set up, you can configure your
72 |RCE| instances to only allow user access from the VPN, to do this see the
72 |RCE| instances to only allow user access from the VPN, to do this see the
73 :ref:`settip-ip-white` section.
73 :ref:`settip-ip-white` section.
74
74
75 Public Key Infrastructure and SSL/TLS Encryption
75 Public Key Infrastructure and SSL/TLS Encryption
76 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
76 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
77
77
78 Public key infrastructure (PKI) is a system that creates, manages, and
78 Public key infrastructure (PKI) is a system that creates, manages, and
79 validates certificates for identifying nodes on a network and encrypting
79 validates certificates for identifying nodes on a network and encrypting
80 communication between them. SSL or TLS certificates can be used to
80 communication between them. SSL or TLS certificates can be used to
81 authenticate different entities with one another. To read more about PKIs,
81 authenticate different entities with one another. To read more about PKIs,
82 see the `OpenSSL PKI tutorial`_ site, or this `Cloudflare PKI post`_.
82 see the `OpenSSL PKI tutorial`_ site, or this `Cloudflare PKI post`_.
83
83
84 If the network you are running is SSL/TLS encrypted, you can configure |RCE|
84 If the network you are running is SSL/TLS encrypted, you can configure |RCE|
85 to always use secure connections using the ``force_https`` and ``use_htsts``
85 to always use secure connections using the ``force_https`` and ``use_htsts``
86 options in the :file:`/home/user/.rccontrol/instance-id/rhodecode.ini` file.
86 options in the :file:`config/_shared/rhodecode.ini` file.
87 For more details, see the :ref:`x-frame` section.
87 For more details, see the :ref:`x-frame` section.
88
88
89 FireWalls and Ports
89 FireWalls and Ports
90 ^^^^^^^^^^^^^^^^^^^
90 ^^^^^^^^^^^^^^^^^^^
91
91
92 Setting up a network firewall for your internal traffic is a good way
92 Setting up a network firewall for your internal traffic is a good way
93 of keeping it secure by blocking off any ports that should not be used.
93 of keeping it secure by blocking off any ports that should not be used.
94 Additionally, you can set non-default ports for certain functions which adds
94 Additionally, you can set non-default ports for certain functions which adds
95 an extra layer of security to your setup.
95 an extra layer of security to your setup.
96
96
97 A well configured firewall will restrict access to everything except the
97 A well configured firewall will restrict access to everything except the
98 services you need to remain open. By exposing fewer services you reduce the
98 services you need to remain open. By exposing fewer services you reduce the
99 number of potential vulnerabilities.
99 number of potential vulnerabilities.
100
100
101 There are a number of different firewall solutions, but for most Linux systems
101 There are a number of different firewall solutions, but for most Linux systems
102 using the built in `IpTables`_ firewall should suffice. On BSD systems you
102 using the built in `IpTables`_ firewall should suffice. On BSD systems you
103 can use `IPFILTER`_ or `IPFW`_. Use the following examples, and the IpTables
103 can use `IPFILTER`_ or `IPFW`_. Use the following examples, and the IpTables
104 documentation to configure your IP Tables on Ubuntu.
104 documentation to configure your IP Tables on Ubuntu.
105
105
106 Changing the default SSH port.
106 Changing the default SSH port.
107
107
108 .. code-block:: bash
108 .. code-block:: bash
109
109
110 # Open SSH config file and change to port 10022
110 # Open SSH config file and change to port 10022
111 vi /etc/ssh/sshd_config
111 vi /etc/ssh/sshd_config
112
112
113 # What ports, IPs and protocols we listen for
113 # What ports, IPs and protocols we listen for
114 Port 10022
114 Port 10022
115
115
116 Setting IP Table rules for SSH traffic. It is important to note that the
116 Setting IP Table rules for SSH traffic. It is important to note that the
117 default policy of your IpTables can differ and it is worth checking how each
117 default policy of your IpTables can differ and it is worth checking how each
118 is configured. The options are *ACCEPT*, *REJECT*, *DROP*, or *LOG*. The
118 is configured. The options are *ACCEPT*, *REJECT*, *DROP*, or *LOG*. The
119 usual practice is to block access on all ports and then enable access only on
119 usual practice is to block access on all ports and then enable access only on
120 the ports you with to expose.
120 the ports you with to expose.
121
121
122 .. code-block:: bash
122 .. code-block:: bash
123
123
124 # Check iptables policy
124 # Check iptables policy
125 $ sudo iptables -L
125 $ sudo iptables -L
126
126
127 Chain INPUT (policy ACCEPT)
127 Chain INPUT (policy ACCEPT)
128 target prot opt source destination
128 target prot opt source destination
129
129
130 Chain FORWARD (policy ACCEPT)
130 Chain FORWARD (policy ACCEPT)
131 target prot opt source destination
131 target prot opt source destination
132
132
133 Chain OUTPUT (policy ACCEPT)
133 Chain OUTPUT (policy ACCEPT)
134 target prot opt source destination
134 target prot opt source destination
135
135
136 # Close all ports by default
136 # Close all ports by default
137 $ sudo iptables -P INPUT DROP
137 $ sudo iptables -P INPUT DROP
138
138
139 $ sudo iptables -L
139 $ sudo iptables -L
140 Chain INPUT (policy DROP)
140 Chain INPUT (policy DROP)
141 target prot opt source destination
141 target prot opt source destination
142 DROP all -- anywhere anywhere
142 DROP all -- anywhere anywhere
143
143
144 Chain FORWARD (policy ACCEPT)
144 Chain FORWARD (policy ACCEPT)
145 target prot opt source destination
145 target prot opt source destination
146
146
147 Chain OUTPUT (policy ACCEPT)
147 Chain OUTPUT (policy ACCEPT)
148 target prot opt source destination
148 target prot opt source destination
149
149
150 .. code-block:: bash
150 .. code-block:: bash
151
151
152 # Deny outbound SSH traffic
152 # Deny outbound SSH traffic
153 sudo iptables -A OUTPUT -p tcp --dport 10022 -j DROP
153 sudo iptables -A OUTPUT -p tcp --dport 10022 -j DROP
154
154
155 # Allow incoming SSH traffic on port 10022
155 # Allow incoming SSH traffic on port 10022
156 sudo iptables -A INPUT -p tcp --dport 10022 -j ACCEPT
156 sudo iptables -A INPUT -p tcp --dport 10022 -j ACCEPT
157
157
158 # Allow incoming HTML traffic on port 80 and 443
158 # Allow incoming HTML traffic on port 80 and 443
159 iptables -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
159 iptables -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
160 iptables -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
160 iptables -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
161
161
162 Saving your IP Table rules, and restoring them from file.
162 Saving your IP Table rules, and restoring them from file.
163
163
164 .. code-block:: bash
164 .. code-block:: bash
165
165
166 # Save you IP Table Rules
166 # Save you IP Table Rules
167 iptables-save
167 iptables-save
168
168
169 # Save your IP Table Rules to a file
169 # Save your IP Table Rules to a file
170 sudo sh -c "iptables-save > /etc/iptables.rules"
170 sudo sh -c "iptables-save > /etc/iptables.rules"
171
171
172 # Restore your IP Table rules from file
172 # Restore your IP Table rules from file
173 iptables-restore < /etc/iptables.rules
173 iptables-restore < /etc/iptables.rules
174
174
175 .. _OpenSSL PKI tutorial: https://pki-tutorial.readthedocs.org/en/latest/#
175 .. _OpenSSL PKI tutorial: https://pki-tutorial.readthedocs.org/en/latest/#
176 .. _Cloudflare PKI post: https://blog.cloudflare.com/how-to-build-your-own-public-key-infrastructure/
176 .. _Cloudflare PKI post: https://blog.cloudflare.com/how-to-build-your-own-public-key-infrastructure/
177 .. _IpTables: https://help.ubuntu.com/community/IptablesHowTo
177 .. _IpTables: https://help.ubuntu.com/community/IptablesHowTo
178 .. _IPFW: https://www.freebsd.org/doc/handbook/firewalls-ipfw.html
178 .. _IPFW: https://www.freebsd.org/doc/handbook/firewalls-ipfw.html
179 .. _IPFILTER: https://www.freebsd.org/doc/handbook/firewalls-ipf.html
179 .. _IPFILTER: https://www.freebsd.org/doc/handbook/firewalls-ipf.html
@@ -1,172 +1,172 b''
1 .. _system-overview-ref:
1 .. _system-overview-ref:
2
2
3 System Overview
3 System Overview
4 ===============
4 ===============
5
5
6 Latest Version
6 Latest Version
7 --------------
7 --------------
8
8
9 * |release| on Unix and Windows systems.
9 * |release| on Unix and Windows systems.
10
10
11 System Architecture
11 System Architecture
12 -------------------
12 -------------------
13
13
14 The following diagram shows a typical production architecture.
14 The following diagram shows a typical production architecture.
15
15
16 .. image:: ../images/architecture-diagram.png
16 .. image:: ../images/architecture-diagram.png
17 :align: center
17 :align: center
18
18
19 Supported Operating Systems
19 Supported Operating Systems
20 ---------------------------
20 ---------------------------
21
21
22 Linux
22 Linux
23 ^^^^^
23 ^^^^^
24
24
25 * Ubuntu 14.04+
25 * Ubuntu 14.04+
26 * CentOS 6.2, 7 and 8
26 * CentOS 6.2, 7 and 8
27 * RHEL 6.2, 7 and 8
27 * RHEL 6.2, 7 and 8
28 * Debian 7.8
28 * Debian 7.8
29 * RedHat Fedora
29 * RedHat Fedora
30 * Arch Linux
30 * Arch Linux
31 * SUSE Linux
31 * SUSE Linux
32
32
33 Windows
33 Windows
34 ^^^^^^^
34 ^^^^^^^
35
35
36 * Windows Vista Ultimate 64bit
36 * Windows Vista Ultimate 64bit
37 * Windows 7 Ultimate 64bit
37 * Windows 7 Ultimate 64bit
38 * Windows 8 Professional 64bit
38 * Windows 8 Professional 64bit
39 * Windows 8.1 Enterprise 64bit
39 * Windows 8.1 Enterprise 64bit
40 * Windows Server 2008 64bit
40 * Windows Server 2008 64bit
41 * Windows Server 2008-R2 64bit
41 * Windows Server 2008-R2 64bit
42 * Windows Server 2012 64bit
42 * Windows Server 2012 64bit
43
43
44 Supported Databases
44 Supported Databases
45 -------------------
45 -------------------
46
46
47 * SQLite
47 * SQLite
48 * MySQL
48 * MySQL
49 * MariaDB
49 * MariaDB
50 * PostgreSQL
50 * PostgreSQL
51
51
52 Supported Browsers
52 Supported Browsers
53 ------------------
53 ------------------
54
54
55 * Chrome
55 * Chrome
56 * Safari
56 * Safari
57 * Firefox
57 * Firefox
58 * Internet Explorer 10 & 11
58 * Internet Explorer 10 & 11
59
59
60 System Requirements
60 System Requirements
61 -------------------
61 -------------------
62
62
63 |RCE| performs best on machines with ultra-fast hard disks. Generally disk
63 |RCE| performs best on machines with ultra-fast hard disks. Generally disk
64 performance is more important than CPU performance. In a corporate production
64 performance is more important than CPU performance. In a corporate production
65 environment handling 1000s of users and |repos| you should deploy on a 12+
65 environment handling 1000s of users and |repos| you should deploy on a 12+
66 core 64GB RAM server. In short, the more RAM the better.
66 core 64GB RAM server. In short, the more RAM the better.
67
67
68
68
69 For example:
69 For example:
70
70
71 - for team of 1 - 5 active users you can run on 1GB RAM machine with 1CPU
71 - for team of 1 - 5 active users you can run on 1GB RAM machine with 1CPU
72 - above 250 active users, |RCE| needs at least 8GB of memory.
72 - above 250 active users, |RCE| needs at least 8GB of memory.
73 Number of CPUs is less important, but recommended to have at least 2-3 CPUs
73 Number of CPUs is less important, but recommended to have at least 2-3 CPUs
74
74
75
75
76 .. _config-rce-files:
76 .. _config-rce-files:
77
77
78 Configuration Files
78 Configuration Files
79 -------------------
79 -------------------
80
80
81 * :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
81 * :file:`config/_shared/rhodecode.ini`
82 * :file:`/home/{user}/.rccontrol/{instance-id}/search_mapping.ini`
82 * :file:`/home/{user}/.rccontrol/{instance-id}/search_mapping.ini`
83 * :file:`/home/{user}/.rccontrol/{vcsserver-id}/vcsserver.ini`
83 * :file:`/home/{user}/.rccontrol/{vcsserver-id}/vcsserver.ini`
84 * :file:`/home/{user}/.rccontrol/supervisor/supervisord.ini`
84 * :file:`/home/{user}/.rccontrol/supervisor/supervisord.ini`
85 * :file:`/home/{user}/.rccontrol.ini`
85 * :file:`/home/{user}/.rccontrol.ini`
86 * :file:`/home/{user}/.rhoderc`
86 * :file:`/home/{user}/.rhoderc`
87 * :file:`/home/{user}/.rccontrol/cache/MANIFEST`
87 * :file:`/home/{user}/.rccontrol/cache/MANIFEST`
88
88
89 For more information, see the :ref:`config-files` section.
89 For more information, see the :ref:`config-files` section.
90
90
91 Log Files
91 Log Files
92 ---------
92 ---------
93
93
94 * :file:`/home/{user}/.rccontrol/{instance-id}/enterprise.log`
94 * :file:`/home/{user}/.rccontrol/{instance-id}/enterprise.log`
95 * :file:`/home/{user}/.rccontrol/{vcsserver-id}/vcsserver.log`
95 * :file:`/home/{user}/.rccontrol/{vcsserver-id}/vcsserver.log`
96 * :file:`/home/{user}/.rccontrol/supervisor/supervisord.log`
96 * :file:`/home/{user}/.rccontrol/supervisor/supervisord.log`
97 * :file:`/tmp/rccontrol.log`
97 * :file:`/tmp/rccontrol.log`
98 * :file:`/tmp/rhodecode_tools.log`
98 * :file:`/tmp/rhodecode_tools.log`
99
99
100 Storage Files
100 Storage Files
101 -------------
101 -------------
102
102
103 * :file:`/home/{user}/.rccontrol/{instance-id}/data/index/{index-file.toc}`
103 * :file:`/home/{user}/.rccontrol/{instance-id}/data/index/{index-file.toc}`
104 * :file:`/home/{user}/repos/.rc_gist_store`
104 * :file:`/home/{user}/repos/.rc_gist_store`
105 * :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.db`
105 * :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.db`
106 * :file:`/opt/rhodecode/store/{unique-hash}`
106 * :file:`/opt/rhodecode/store/{unique-hash}`
107
107
108 Default Repositories Location
108 Default Repositories Location
109 -----------------------------
109 -----------------------------
110
110
111 * :file:`/home/{user}/repos`
111 * :file:`/home/{user}/repos`
112
112
113 Connection Methods
113 Connection Methods
114 ------------------
114 ------------------
115
115
116 * HTTPS
116 * HTTPS
117 * SSH
117 * SSH
118 * |RCE| API
118 * |RCE| API
119
119
120 Internationalization Support
120 Internationalization Support
121 ----------------------------
121 ----------------------------
122
122
123 Currently available in the following languages, see `Transifex`_ for the
123 Currently available in the following languages, see `Transifex`_ for the
124 latest details. If you want a new language added, please contact us. To
124 latest details. If you want a new language added, please contact us. To
125 configure your language settings, see the :ref:`set-lang` section.
125 configure your language settings, see the :ref:`set-lang` section.
126
126
127 .. hlist::
127 .. hlist::
128
128
129 * Belorussian
129 * Belorussian
130 * Chinese
130 * Chinese
131 * French
131 * French
132 * German
132 * German
133 * Italian
133 * Italian
134 * Japanese
134 * Japanese
135 * Portuguese
135 * Portuguese
136 * Polish
136 * Polish
137 * Russian
137 * Russian
138 * Spanish
138 * Spanish
139
139
140 Licencing Information
140 Licencing Information
141 ---------------------
141 ---------------------
142
142
143 * See licencing information `here`_
143 * See licencing information `here`_
144
144
145 Peer-to-peer Failover Support
145 Peer-to-peer Failover Support
146 -----------------------------
146 -----------------------------
147
147
148 * Yes
148 * Yes
149
149
150 Additional Binaries
150 Additional Binaries
151 -------------------
151 -------------------
152
152
153 * Yes, see :ref:`rhodecode-nix-ref` for full details.
153 * Yes, see :ref:`rhodecode-nix-ref` for full details.
154
154
155 Remote Connectivity
155 Remote Connectivity
156 -------------------
156 -------------------
157
157
158 * Available
158 * Available
159
159
160 Executable Files
160 Executable Files
161 ----------------
161 ----------------
162
162
163 Windows: :file:`RhodeCode-installer-{version}.exe`
163 Windows: :file:`RhodeCode-installer-{version}.exe`
164
164
165 Deprecated Support
165 Deprecated Support
166 ------------------
166 ------------------
167
167
168 - Internet Explorer 8 support deprecated since version 3.7.0.
168 - Internet Explorer 8 support deprecated since version 3.7.0.
169 - Internet Explorer 9 support deprecated since version 3.8.0.
169 - Internet Explorer 9 support deprecated since version 3.8.0.
170
170
171 .. _here: https://rhodecode.com/licenses/
171 .. _here: https://rhodecode.com/licenses/
172 .. _Transifex: https://explore.transifex.com/rhodecode/RhodeCode/
172 .. _Transifex: https://explore.transifex.com/rhodecode/RhodeCode/
@@ -1,300 +1,300 b''
1 .. _admin-tricks:
1 .. _admin-tricks:
2
2
3 One-time Admin Tasks
3 One-time Admin Tasks
4 --------------------
4 --------------------
5
5
6 * :ref:`web-analytics`
6 * :ref:`web-analytics`
7 * :ref:`admin-tricks-license`
7 * :ref:`admin-tricks-license`
8 * :ref:`announcements`
8 * :ref:`announcements`
9 * :ref:`md-rst`
9 * :ref:`md-rst`
10 * :ref:`repo-stats`
10 * :ref:`repo-stats`
11 * :ref:`server-side-merge`
11 * :ref:`server-side-merge`
12 * :ref:`remap-rescan`
12 * :ref:`remap-rescan`
13 * :ref:`custom-hooks`
13 * :ref:`custom-hooks`
14 * :ref:`clear-repo-cache`
14 * :ref:`clear-repo-cache`
15 * :ref:`set-repo-pub`
15 * :ref:`set-repo-pub`
16 * :ref:`ping`
16 * :ref:`ping`
17
17
18 .. _web-analytics:
18 .. _web-analytics:
19
19
20 Adding Web Analytics
20 Adding Web Analytics
21 ^^^^^^^^^^^^^^^^^^^^
21 ^^^^^^^^^^^^^^^^^^^^
22
22
23 If you wish to add a Google Analytics, or any other kind of tracker to your
23 If you wish to add a Google Analytics, or any other kind of tracker to your
24 |RCE| instance you can add the necessary codes to the header or footer
24 |RCE| instance you can add the necessary codes to the header or footer
25 section of each instance using the following steps:
25 section of each instance using the following steps:
26
26
27 1. From the |RCE| interface, select
27 1. From the |RCE| interface, select
28 :menuselection:`Admin --> Settings --> Global`
28 :menuselection:`Admin --> Settings --> Global`
29 2. To add a tracking code to you instance, enter it in the header or footer
29 2. To add a tracking code to you instance, enter it in the header or footer
30 section and select **Save**
30 section and select **Save**
31
31
32 Use the example templates in the drop-down menu to set up your configuration.
32 Use the example templates in the drop-down menu to set up your configuration.
33
33
34 .. _admin-tricks-license:
34 .. _admin-tricks-license:
35
35
36 Licence Key Management
36 Licence Key Management
37 ^^^^^^^^^^^^^^^^^^^^^^
37 ^^^^^^^^^^^^^^^^^^^^^^
38
38
39 To manage your license key, go to
39 To manage your license key, go to
40 :menuselection:`Admin --> Settings --> License`.
40 :menuselection:`Admin --> Settings --> License`.
41 On this page you can see the license key details. If you need a new license,
41 On this page you can see the license key details. If you need a new license,
42 or have questions about your current one, contact support@rhodecode.com
42 or have questions about your current one, contact support@rhodecode.com
43
43
44 .. _announcements:
44 .. _announcements:
45
45
46 Server-wide Announcements
46 Server-wide Announcements
47 ^^^^^^^^^^^^^^^^^^^^^^^^^
47 ^^^^^^^^^^^^^^^^^^^^^^^^^
48
48
49 If you need to make a server-wide announcement to all users,
49 If you need to make a server-wide announcement to all users,
50 you can add a message to be displayed using the following steps:
50 you can add a message to be displayed using the following steps:
51
51
52 1. From the |RCE| interface, select
52 1. From the |RCE| interface, select
53 :menuselection:`Admin --> Settings --> Global`
53 :menuselection:`Admin --> Settings --> Global`
54 2. To add a message that will be displayed to all users,
54 2. To add a message that will be displayed to all users,
55 select :guilabel:`Server Announcement` from the drop-down menu and
55 select :guilabel:`Server Announcement` from the drop-down menu and
56 change the ``var message = "TYPE YOUR MESSAGE HERE";`` example line.
56 change the ``var message = "TYPE YOUR MESSAGE HERE";`` example line.
57 3. Select :guilabel:`Save`, and you will see the message once your page
57 3. Select :guilabel:`Save`, and you will see the message once your page
58 refreshes.
58 refreshes.
59
59
60 .. image:: ../../images/server-wide-announcement.png
60 .. image:: ../../images/server-wide-announcement.png
61 :alt: Server Wide Announcement
61 :alt: Server Wide Announcement
62
62
63 .. _md-rst:
63 .. _md-rst:
64
64
65
65
66 Suppress license warnings or errors
66 Suppress license warnings or errors
67 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
67 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
68
68
69 In case you're running on maximum allowed users, RhodeCode will display a
69 In case you're running on maximum allowed users, RhodeCode will display a
70 warning message on pages that you're close to the license limits.
70 warning message on pages that you're close to the license limits.
71 It's often not desired to show that all the time. Here's how you can suppress
71 It's often not desired to show that all the time. Here's how you can suppress
72 the license messages.
72 the license messages.
73
73
74 1. From the |RCE| interface, select
74 1. From the |RCE| interface, select
75 :menuselection:`Admin --> Settings --> Global`
75 :menuselection:`Admin --> Settings --> Global`
76 2. Select :guilabel:`Flash message filtering` from the drop-down menu.
76 2. Select :guilabel:`Flash message filtering` from the drop-down menu.
77 3. Select :guilabel:`Save`, and you will no longer see the license message
77 3. Select :guilabel:`Save`, and you will no longer see the license message
78 once your page refreshes.
78 once your page refreshes.
79
79
80 .. _admin-tricks-suppress-license-messages:
80 .. _admin-tricks-suppress-license-messages:
81
81
82
82
83 Markdown or RST Rendering
83 Markdown or RST Rendering
84 ^^^^^^^^^^^^^^^^^^^^^^^^^
84 ^^^^^^^^^^^^^^^^^^^^^^^^^
85
85
86 |RCE| can use `Markdown`_ or `reStructured Text`_ in commit message,
86 |RCE| can use `Markdown`_ or `reStructured Text`_ in commit message,
87 code review messages, and inline comments. To set the default to either,
87 code review messages, and inline comments. To set the default to either,
88 select your preference from the drop-down menu on the
88 select your preference from the drop-down menu on the
89 :menuselection:`Admin --> Settings --> Visual` page and select
89 :menuselection:`Admin --> Settings --> Visual` page and select
90 :guilabel:`Save settings`.
90 :guilabel:`Save settings`.
91
91
92 .. _repo-stats:
92 .. _repo-stats:
93
93
94 Enabling Repository Statistics
94 Enabling Repository Statistics
95 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
95 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
96
96
97 To enable |repo| statistics, use the following steps:
97 To enable |repo| statistics, use the following steps:
98
98
99 1. From the |RCE| interface, open
99 1. From the |RCE| interface, open
100 :menuselection:`Admin --> Repositories` and select
100 :menuselection:`Admin --> Repositories` and select
101 :guilabel:`Edit` beside the |repo| for which you wish to enable statistics.
101 :guilabel:`Edit` beside the |repo| for which you wish to enable statistics.
102 2. Check the :guilabel:`Enable statistics` box, and select :guilabel:`Save`
102 2. Check the :guilabel:`Enable statistics` box, and select :guilabel:`Save`
103
103
104 .. _server-side-merge:
104 .. _server-side-merge:
105
105
106 Enabling Server-side Merging
106 Enabling Server-side Merging
107 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
107 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
108
108
109 To enable server-side merging, use the following steps:
109 To enable server-side merging, use the following steps:
110
110
111 1. From the |RCE| interface, open :menuselection:`Admin --> Settings --> VCS`
111 1. From the |RCE| interface, open :menuselection:`Admin --> Settings --> VCS`
112 2. Check the :guilabel:`Server-side merge` box, and select
112 2. Check the :guilabel:`Server-side merge` box, and select
113 :guilabel:`Save Settings`
113 :guilabel:`Save Settings`
114
114
115 If you encounter slow performance with server-side merging enabled, check the
115 If you encounter slow performance with server-side merging enabled, check the
116 speed at which your server is performing actions. When server-side merging is
116 speed at which your server is performing actions. When server-side merging is
117 enabled, the following actions occurs on the server.
117 enabled, the following actions occurs on the server.
118
118
119 * A |pr| is created in the database.
119 * A |pr| is created in the database.
120 * A shadow |repo| is created as a working environment for the |pr|.
120 * A shadow |repo| is created as a working environment for the |pr|.
121 * On display, |RCE| checks if the |pr| can be merged.
121 * On display, |RCE| checks if the |pr| can be merged.
122
122
123 To check how fast the shadow |repo| creation is occurring on your server, use
123 To check how fast the shadow |repo| creation is occurring on your server, use
124 the following steps:
124 the following steps:
125
125
126 1. Log into your server and create a directory in your |repos| folder.
126 1. Log into your server and create a directory in your |repos| folder.
127 2. Clone a |repo| that is showing slow performance and time the action.
127 2. Clone a |repo| that is showing slow performance and time the action.
128
128
129 .. code-block:: bash
129 .. code-block:: bash
130
130
131 # One option is to use the time command
131 # One option is to use the time command
132 $ time hg clone SOURCE_REPO TARGET
132 $ time hg clone SOURCE_REPO TARGET
133
133
134 .. _remap-rescan:
134 .. _remap-rescan:
135
135
136 Remap and Rescan Repositories
136 Remap and Rescan Repositories
137 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
137 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
138
138
139 You may want to Remap and rescan the |repos| that |RCE| is managing to ensure
139 You may want to Remap and rescan the |repos| that |RCE| is managing to ensure
140 the system is always up-to-date. This is useful after importing, deleting,
140 the system is always up-to-date. This is useful after importing, deleting,
141 or carrying out general cleaning up operations. To do this use the
141 or carrying out general cleaning up operations. To do this use the
142 following steps:
142 following steps:
143
143
144 1. From the |RCE|, open
144 1. From the |RCE|, open
145 :menuselection:`Admin --> Settings --> Remap and rescan`
145 :menuselection:`Admin --> Settings --> Remap and rescan`
146 2. Click :guilabel:`Rescan Repositories`
146 2. Click :guilabel:`Rescan Repositories`
147
147
148 Check the additional options if needed:
148 Check the additional options if needed:
149
149
150 * :guilabel:`Destroy old data`: Useful for purging deleted repository
150 * :guilabel:`Destroy old data`: Useful for purging deleted repository
151 information from the database.
151 information from the database.
152 * :guilabel:`Invalidate cache for all repositories`: Use this to completely
152 * :guilabel:`Invalidate cache for all repositories`: Use this to completely
153 remap all |repos|. Useful when importing or migrating |repos| to ensure all
153 remap all |repos|. Useful when importing or migrating |repos| to ensure all
154 new information is picked up.
154 new information is picked up.
155
155
156 .. _custom-hooks:
156 .. _custom-hooks:
157
157
158 Adding Custom Hooks
158 Adding Custom Hooks
159 ^^^^^^^^^^^^^^^^^^^
159 ^^^^^^^^^^^^^^^^^^^
160
160
161 To add custom hooks to your instance, use the following steps:
161 To add custom hooks to your instance, use the following steps:
162
162
163 1. Open :menuselection:`Admin --> Settings --> Hooks`
163 1. Open :menuselection:`Admin --> Settings --> Hooks`
164 2. Add your custom hook details, you can use a file path to specify custom
164 2. Add your custom hook details, you can use a file path to specify custom
165 hook scripts, for example:
165 hook scripts, for example:
166 ``pretxnchangegroup.example`` with value ``python:/path/to/custom_hook.py:my_func_name``
166 ``pretxnchangegroup.example`` with value ``python:/path/to/custom_hook.py:my_func_name``
167 3. Select :guilabel:`Save`
167 3. Select :guilabel:`Save`
168
168
169 Also, see the RhodeCode Extensions section of the :ref:`rc-tools` guide. RhodeCode
169 Also, see the RhodeCode Extensions section of the :ref:`rc-tools` guide. RhodeCode
170 Extensions can be used to add additional hooks to your instance and comes
170 Extensions can be used to add additional hooks to your instance and comes
171 with a number of pre-built plugins if you chose to install them.
171 with a number of pre-built plugins if you chose to install them.
172
172
173 .. _clear-repo-cache:
173 .. _clear-repo-cache:
174
174
175 Clearing |repo| cache
175 Clearing |repo| cache
176 ^^^^^^^^^^^^^^^^^^^^^
176 ^^^^^^^^^^^^^^^^^^^^^
177
177
178 If you need to clear the cache for a particular |repo|, use the following steps:
178 If you need to clear the cache for a particular |repo|, use the following steps:
179
179
180 1. Open :menuselection:`Admin --> Repositories` and select :guilabel:`Edit`
180 1. Open :menuselection:`Admin --> Repositories` and select :guilabel:`Edit`
181 beside the |repo| whose cache you wish to clear.
181 beside the |repo| whose cache you wish to clear.
182 2. On the |repo| settings page, go to the :guilabel:`Caches` tab and select
182 2. On the |repo| settings page, go to the :guilabel:`Caches` tab and select
183 :guilabel:`Invalidate repository cache`.
183 :guilabel:`Invalidate repository cache`.
184
184
185 .. _set-lang:
185 .. _set-lang:
186
186
187 Changing Default Language
187 Changing Default Language
188 ^^^^^^^^^^^^^^^^^^^^^^^^^
188 ^^^^^^^^^^^^^^^^^^^^^^^^^
189
189
190 To change the default language of a |RCE| instance, change the language code
190 To change the default language of a |RCE| instance, change the language code
191 in the :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file. To
191 in the :file:`config/_shared/rhodecode.ini` file. To
192 do this, use the following steps.
192 do this, use the following steps.
193
193
194 1. Open the :file:`rhodecode.ini` file and set the required language code.
194 1. Open the :file:`rhodecode.ini` file and set the required language code.
195
195
196 .. code-block:: ini
196 .. code-block:: ini
197
197
198 ## Optional Languages
198 ## Optional Languages
199 ## en(default), de, fr, it, ja, pl, pt, ru, zh
199 ## en(default), de, fr, it, ja, pl, pt, ru, zh
200 lang = de
200 lang = de
201
201
202 2. Restart the |RCE| instance and check that the language has been updated.
202 2. Restart the |RCE| instance and check that the language has been updated.
203
203
204 .. code-block:: bash
204 .. code-block:: bash
205
205
206 $ rccontrol restart enterprise-2
206 $ rccontrol restart enterprise-2
207 Instance "enterprise-2" successfully stopped.
207 Instance "enterprise-2" successfully stopped.
208 Instance "enterprise-2" successfully started.
208 Instance "enterprise-2" successfully started.
209
209
210 .. image:: ../../images/language.png
210 .. image:: ../../images/language.png
211
211
212 .. _set-repo-pub:
212 .. _set-repo-pub:
213
213
214 Setting Repositories to Publish
214 Setting Repositories to Publish
215 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
215 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
216
216
217 To automatically promote your local |repos| to public after pushing to |RCE|,
217 To automatically promote your local |repos| to public after pushing to |RCE|,
218 enable the :guilabel:`Set repositories as publishing` option on the
218 enable the :guilabel:`Set repositories as publishing` option on the
219 :menuselection:`Admin --> Settings --> VCS` page.
219 :menuselection:`Admin --> Settings --> VCS` page.
220
220
221 .. note::
221 .. note::
222
222
223 This option is enabled by default on most |RCE| versions, but if upgrading
223 This option is enabled by default on most |RCE| versions, but if upgrading
224 from a 1.7.x version it could be disabled on upgrade due to inheriting
224 from a 1.7.x version it could be disabled on upgrade due to inheriting
225 older default settings.
225 older default settings.
226
226
227 .. _ping:
227 .. _ping:
228
228
229 Pinging the |RCE| Server
229 Pinging the |RCE| Server
230 ^^^^^^^^^^^^^^^^^^^^^^^^
230 ^^^^^^^^^^^^^^^^^^^^^^^^
231
231
232 You can check the IP Address of your |RCE| instance using the
232 You can check the IP Address of your |RCE| instance using the
233 following URL: ``{instance-URL}/_admin/ping``.
233 following URL: ``{instance-URL}/_admin/ping``.
234
234
235 .. code-block:: bash
235 .. code-block:: bash
236
236
237 $ curl https://your.rhodecode.url/_admin/ping
237 $ curl https://your.rhodecode.url/_admin/ping
238 pong[rce-7880] => 203.0.113.23
238 pong[rce-7880] => 203.0.113.23
239
239
240 .. _Markdown: http://daringfireball.net/projects/markdown/
240 .. _Markdown: http://daringfireball.net/projects/markdown/
241 .. _reStructured Text: http://docutils.sourceforge.io/docs/index.html
241 .. _reStructured Text: http://docutils.sourceforge.io/docs/index.html
242
242
243
243
244 Unarchiving a repository
244 Unarchiving a repository
245 ^^^^^^^^^^^^^^^^^^^^^^^^^
245 ^^^^^^^^^^^^^^^^^^^^^^^^^
246
246
247 Archive operation for the repository is similar as delete. Archive keeps the data for future references
247 Archive operation for the repository is similar as delete. Archive keeps the data for future references
248 but makes the repository read-only. After archiving the repository it shouldn't be modified in any way.
248 but makes the repository read-only. After archiving the repository it shouldn't be modified in any way.
249 This is why repository settings are disabled for an archived repository.
249 This is why repository settings are disabled for an archived repository.
250
250
251 If there's a need for unarchiving a repository for some reasons, the interactive
251 If there's a need for unarchiving a repository for some reasons, the interactive
252 ishell interface should be used.
252 ishell interface should be used.
253
253
254 .. code-block:: bash
254 .. code-block:: bash
255
255
256 # Open iShell from the terminal
256 # Open iShell from the terminal
257 $ rccontrol ishell enterprise-1/community-1
257 $ rccontrol ishell enterprise-1/community-1
258
258
259 .. code-block:: python
259 .. code-block:: python
260
260
261 # Set repository as un-archived
261 # Set repository as un-archived
262 In [1]: repo = Repository.get_by_repo_name('SOME_REPO_NAME')
262 In [1]: repo = Repository.get_by_repo_name('SOME_REPO_NAME')
263 In [2]: repo.archived = False
263 In [2]: repo.archived = False
264 In [3]: Session().add(repo);Session().commit()
264 In [3]: Session().add(repo);Session().commit()
265
265
266
266
267
267
268
268
269 Bulk change repository owner
269 Bulk change repository owner
270 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
270 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
271
271
272 Here's how one can change an owner of repository for an user who has been de activated.
272 Here's how one can change an owner of repository for an user who has been de activated.
273 Settings a new owner can be done via ishell for all repositories that past owner had.
273 Settings a new owner can be done via ishell for all repositories that past owner had.
274
274
275 do run this script the interactive ishell interface should be used.
275 do run this script the interactive ishell interface should be used.
276
276
277 .. code-block:: bash
277 .. code-block:: bash
278
278
279 # Open iShell from the terminal
279 # Open iShell from the terminal
280 $ rccontrol ishell enterprise-1/community-1
280 $ rccontrol ishell enterprise-1/community-1
281
281
282
282
283 .. code-block:: python
283 .. code-block:: python
284
284
285 from rhodecode.model.db import User, Repository, Session
285 from rhodecode.model.db import User, Repository, Session
286 from rhodecode.model.permission import PermissionModel
286 from rhodecode.model.permission import PermissionModel
287
287
288 # replace old-owner and new-owner with your exact users
288 # replace old-owner and new-owner with your exact users
289 old_owner = User.get_by_username('old-owner')
289 old_owner = User.get_by_username('old-owner')
290 new_owner = User.get_by_username('new-owner')
290 new_owner = User.get_by_username('new-owner')
291
291
292 # list of users we need to "flush" permissions
292 # list of users we need to "flush" permissions
293 affected_user_ids = [new_owner.user_id, old_owner.user_id]
293 affected_user_ids = [new_owner.user_id, old_owner.user_id]
294
294
295 for repo in Repository.get_all_repos(user_id=old_owner.user_id):
295 for repo in Repository.get_all_repos(user_id=old_owner.user_id):
296 repo.user = new_owner
296 repo.user = new_owner
297 Session().add(repo)
297 Session().add(repo)
298 Session().commit()
298 Session().commit()
299
299
300 PermissionModel().trigger_permission_flush(affected_user_ids)
300 PermissionModel().trigger_permission_flush(affected_user_ids)
@@ -1,74 +1,74 b''
1 .. _config-files:
1 .. _config-files:
2
2
3 Configuration Files Overview
3 Configuration Files Overview
4 ============================
4 ============================
5
5
6 |RCE| and |RCC| have a number of different configuration files. The following
6 |RCE| and |RCC| have a number of different configuration files. The following
7 is a brief explanation of each, and links to their associated configuration
7 is a brief explanation of each, and links to their associated configuration
8 sections.
8 sections.
9
9
10 .. rst-class:: dl-horizontal
10 .. rst-class:: dl-horizontal
11
11
12 \- **rhodecode.ini**
12 \- **rhodecode.ini**
13 Default location:
13 Default location:
14 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
14 :file:`config/_shared/rhodecode.ini`
15
15
16 This is the main |RCE| configuration file and controls much of its
16 This is the main |RCE| configuration file and controls much of its
17 default behaviour. It is also used to configure certain customer
17 default behaviour. It is also used to configure certain customer
18 settings. Here are some of the most common reasons to make changes to
18 settings. Here are some of the most common reasons to make changes to
19 this file.
19 this file.
20
20
21 * :ref:`config-database`
21 * :ref:`config-database`
22 * :ref:`set-up-mail`
22 * :ref:`set-up-mail`
23 * :ref:`increase-gunicorn`
23 * :ref:`increase-gunicorn`
24 * :ref:`x-frame`
24 * :ref:`x-frame`
25
25
26 \- **search_mapping.ini**
26 \- **search_mapping.ini**
27 Default location:
27 Default location:
28 :file:`/home/{user}/.rccontrol/{instance-id}/search_mapping.ini`
28 :file:`/home/{user}/.rccontrol/{instance-id}/search_mapping.ini`
29
29
30 This file is used to control the |RCE| indexer. It comes configured
30 This file is used to control the |RCE| indexer. It comes configured
31 to index your instance. To change the default configuration, see
31 to index your instance. To change the default configuration, see
32 :ref:`advanced-indexing`.
32 :ref:`advanced-indexing`.
33
33
34 \- **vcsserver.ini**
34 \- **vcsserver.ini**
35 Default location:
35 Default location:
36 :file:`/home/{user}/.rccontrol/{vcsserver-id}/vcsserver.ini`
36 :file:`/home/{user}/.rccontrol/{vcsserver-id}/vcsserver.ini`
37
37
38 The VCS Server handles the connection between your |repos| and |RCE|.
38 The VCS Server handles the connection between your |repos| and |RCE|.
39 See the :ref:`vcs-server` section for configuration options and more
39 See the :ref:`vcs-server` section for configuration options and more
40 detailed information.
40 detailed information.
41
41
42 \- **supervisord.ini**
42 \- **supervisord.ini**
43 Default location:
43 Default location:
44 :file:`/home/{user}/.rccontrol/supervisor/supervisord.ini`
44 :file:`/home/{user}/.rccontrol/supervisor/supervisord.ini`
45
45
46 |RCC| uses Supervisor to monitor and manage installed instances of
46 |RCC| uses Supervisor to monitor and manage installed instances of
47 |RCE| and the VCS Server. |RCC| will manage this file completely,
47 |RCE| and the VCS Server. |RCC| will manage this file completely,
48 unless you install |RCE| in self-managed mode. For more information,
48 unless you install |RCE| in self-managed mode. For more information,
49 see the :ref:`Supervisor Setup<control:supervisor-setup>` section.
49 see the :ref:`Supervisor Setup<control:supervisor-setup>` section.
50
50
51 \- **.rccontrol.ini**
51 \- **.rccontrol.ini**
52 Default location: :file:`/home/{user}/.rccontrol.ini`
52 Default location: :file:`/home/{user}/.rccontrol.ini`
53
53
54 This file contains the instances that |RCC| starts at boot, which is all
54 This file contains the instances that |RCC| starts at boot, which is all
55 by default, but for more information, see
55 by default, but for more information, see
56 the :ref:`Manually Start At Boot <control:set-start-boot>` section.
56 the :ref:`Manually Start At Boot <control:set-start-boot>` section.
57
57
58 \- **.rhoderc**
58 \- **.rhoderc**
59 Default location: :file:`/home/{user}/.rhoderc`
59 Default location: :file:`/home/{user}/.rhoderc`
60
60
61 This file is used by the |RCE| API when accessing an instance from a
61 This file is used by the |RCE| API when accessing an instance from a
62 remote machine. The API checks this file for connection and
62 remote machine. The API checks this file for connection and
63 authentication details. For more details, see the :ref:`config-rhoderc`
63 authentication details. For more details, see the :ref:`config-rhoderc`
64 section.
64 section.
65
65
66 \- **MANIFEST**
66 \- **MANIFEST**
67 Default location: :file:`/home/{user}/.rccontrol/cache/MANIFEST`
67 Default location: :file:`/home/{user}/.rccontrol/cache/MANIFEST`
68
68
69 |RCC| uses this file to source the latest available builds from the
69 |RCC| uses this file to source the latest available builds from the
70 secure RhodeCode download channels. The only reason to mess with this file
70 secure RhodeCode download channels. The only reason to mess with this file
71 is if you need to do an offline installation,
71 is if you need to do an offline installation,
72 see the :ref:`Offline Installation<control:offline-installer-ref>`
72 see the :ref:`Offline Installation<control:offline-installer-ref>`
73 instructions, otherwise |RCC| will completely manage this file.
73 instructions, otherwise |RCC| will completely manage this file.
74
74
@@ -1,148 +1,148 b''
1 .. _debug-mode:
1 .. _debug-mode:
2
2
3 Enabling Debug Mode
3 Enabling Debug Mode
4 -------------------
4 -------------------
5
5
6 Debug Mode will enable debug logging, and request tracking middleware. Debug Mode
6 Debug Mode will enable debug logging, and request tracking middleware. Debug Mode
7 enabled DEBUG log-level which allows tracking various information about authentication
7 enabled DEBUG log-level which allows tracking various information about authentication
8 failures, LDAP connection, email etc.
8 failures, LDAP connection, email etc.
9
9
10 The request tracking will add a special
10 The request tracking will add a special
11 unique ID: `| req_id:00000000-0000-0000-0000-000000000000` at the end of each log line.
11 unique ID: `| req_id:00000000-0000-0000-0000-000000000000` at the end of each log line.
12 The req_id is the same for each individual requests, it means that if you want to
12 The req_id is the same for each individual requests, it means that if you want to
13 track particular user logs only, and exclude other concurrent ones
13 track particular user logs only, and exclude other concurrent ones
14 simply grep by `req_id` uuid which you'll have to find for the individual request.
14 simply grep by `req_id` uuid which you'll have to find for the individual request.
15
15
16 To enable debug mode on a |RCE| instance you need to set the debug property
16 To enable debug mode on a |RCE| instance you need to set the debug property
17 in the :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file. To
17 in the :file:`config/_shared/rhodecode.ini` file. To
18 do this, use the following steps
18 do this, use the following steps
19
19
20 1. Open the file and set the ``debug`` line to ``true``
20 1. Open the file and set the ``debug`` line to ``true``
21 2. Restart you instance using the ``rccontrol restart`` command,
21 2. Restart you instance using the ``rccontrol restart`` command,
22 see the following example:
22 see the following example:
23
23
24 .. code-block:: ini
24 .. code-block:: ini
25
25
26 [DEFAULT]
26 [DEFAULT]
27 debug = true
27 debug = true
28
28
29 .. code-block:: bash
29 .. code-block:: bash
30
30
31 # Restart your instance
31 # Restart your instance
32 $ rccontrol restart enterprise-1
32 $ rccontrol restart enterprise-1
33 Instance "enterprise-1" successfully stopped.
33 Instance "enterprise-1" successfully stopped.
34 Instance "enterprise-1" successfully started.
34 Instance "enterprise-1" successfully started.
35
35
36
36
37 Debug and Logging Configuration
37 Debug and Logging Configuration
38 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
38 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
39
39
40 Further debugging and logging settings can also be set in the
40 Further debugging and logging settings can also be set in the
41 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file.
41 :file:`config/_shared/rhodecode.ini` file.
42
42
43 In the logging section, the various packages that run with |RCE| can have
43 In the logging section, the various packages that run with |RCE| can have
44 different debug levels set. If you want to increase the logging level change
44 different debug levels set. If you want to increase the logging level change
45 ``level = DEBUG`` line to one of the valid options.
45 ``level = DEBUG`` line to one of the valid options.
46
46
47 You also need to change the log level for handlers. See the example
47 You also need to change the log level for handlers. See the example
48 ``##handler`` section below. The ``handler`` level takes the same options as
48 ``##handler`` section below. The ``handler`` level takes the same options as
49 the ``debug`` level.
49 the ``debug`` level.
50
50
51 .. code-block:: ini
51 .. code-block:: ini
52
52
53 ################################
53 ################################
54 ### LOGGING CONFIGURATION ####
54 ### LOGGING CONFIGURATION ####
55 ################################
55 ################################
56 [loggers]
56 [loggers]
57 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
57 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
58
58
59 [handlers]
59 [handlers]
60 keys = console, console_sql, file, file_rotating
60 keys = console, console_sql, file, file_rotating
61
61
62 [formatters]
62 [formatters]
63 keys = generic, color_formatter, color_formatter_sql
63 keys = generic, color_formatter, color_formatter_sql
64
64
65 #############
65 #############
66 ## LOGGERS ##
66 ## LOGGERS ##
67 #############
67 #############
68 [logger_root]
68 [logger_root]
69 level = NOTSET
69 level = NOTSET
70 handlers = console
70 handlers = console
71
71
72 [logger_sqlalchemy]
72 [logger_sqlalchemy]
73 level = INFO
73 level = INFO
74 handlers = console_sql
74 handlers = console_sql
75 qualname = sqlalchemy.engine
75 qualname = sqlalchemy.engine
76 propagate = 0
76 propagate = 0
77
77
78 [logger_beaker]
78 [logger_beaker]
79 level = DEBUG
79 level = DEBUG
80 handlers =
80 handlers =
81 qualname = beaker.container
81 qualname = beaker.container
82 propagate = 1
82 propagate = 1
83
83
84 [logger_rhodecode]
84 [logger_rhodecode]
85 level = DEBUG
85 level = DEBUG
86 handlers =
86 handlers =
87 qualname = rhodecode
87 qualname = rhodecode
88 propagate = 1
88 propagate = 1
89
89
90 [logger_ssh_wrapper]
90 [logger_ssh_wrapper]
91 level = DEBUG
91 level = DEBUG
92 handlers =
92 handlers =
93 qualname = ssh_wrapper
93 qualname = ssh_wrapper
94 propagate = 1
94 propagate = 1
95
95
96 [logger_celery]
96 [logger_celery]
97 level = DEBUG
97 level = DEBUG
98 handlers =
98 handlers =
99 qualname = celery
99 qualname = celery
100
100
101 ##############
101 ##############
102 ## HANDLERS ##
102 ## HANDLERS ##
103 ##############
103 ##############
104
104
105 [handler_console]
105 [handler_console]
106 class = StreamHandler
106 class = StreamHandler
107 args = (sys.stderr, )
107 args = (sys.stderr, )
108 level = DEBUG
108 level = DEBUG
109 formatter = generic
109 formatter = generic
110
110
111 [handler_console_sql]
111 [handler_console_sql]
112 class = StreamHandler
112 class = StreamHandler
113 args = (sys.stderr, )
113 args = (sys.stderr, )
114 level = INFO
114 level = INFO
115 formatter = generic
115 formatter = generic
116
116
117 [handler_file]
117 [handler_file]
118 class = FileHandler
118 class = FileHandler
119 args = ('rhodecode_debug.log', 'a',)
119 args = ('rhodecode_debug.log', 'a',)
120 level = INFO
120 level = INFO
121 formatter = generic
121 formatter = generic
122
122
123 [handler_file_rotating]
123 [handler_file_rotating]
124 class = logging.handlers.TimedRotatingFileHandler
124 class = logging.handlers.TimedRotatingFileHandler
125 # 'D', 5 - rotate every 5days
125 # 'D', 5 - rotate every 5days
126 # you can set 'h', 'midnight'
126 # you can set 'h', 'midnight'
127 args = ('rhodecode_debug_rotated.log', 'D', 5, 10,)
127 args = ('rhodecode_debug_rotated.log', 'D', 5, 10,)
128 level = INFO
128 level = INFO
129 formatter = generic
129 formatter = generic
130
130
131 ################
131 ################
132 ## FORMATTERS ##
132 ## FORMATTERS ##
133 ################
133 ################
134
134
135 [formatter_generic]
135 [formatter_generic]
136 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
136 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
137 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
137 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
138 datefmt = %Y-%m-%d %H:%M:%S
138 datefmt = %Y-%m-%d %H:%M:%S
139
139
140 [formatter_color_formatter]
140 [formatter_color_formatter]
141 class = rhodecode.lib.logging_formatter.ColorFormatter
141 class = rhodecode.lib.logging_formatter.ColorFormatter
142 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
142 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
143 datefmt = %Y-%m-%d %H:%M:%S
143 datefmt = %Y-%m-%d %H:%M:%S
144
144
145 [formatter_color_formatter_sql]
145 [formatter_color_formatter_sql]
146 class = rhodecode.lib.logging_formatter.ColorFormatterSql
146 class = rhodecode.lib.logging_formatter.ColorFormatterSql
147 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
147 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
148 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
148 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,208 +1,208 b''
1 .. _svn-http:
1 .. _svn-http:
2
2
3 |svn| With Write Over HTTP
3 |svn| With Write Over HTTP
4 ^^^^^^^^^^^^^^^^^^^^^^^^^^
4 ^^^^^^^^^^^^^^^^^^^^^^^^^^
5
5
6 To use |svn| with read/write support over the |svn| HTTP protocol, you have to
6 To use |svn| with read/write support over the |svn| HTTP protocol, you have to
7 configure the HTTP |svn| backend.
7 configure the HTTP |svn| backend.
8
8
9 Prerequisites
9 Prerequisites
10 =============
10 =============
11
11
12 - Enable HTTP support inside the admin VCS settings on your |RCE| instance
12 - Enable HTTP support inside the admin VCS settings on your |RCE| instance
13 - You need to install the following tools on the machine that is running an
13 - You need to install the following tools on the machine that is running an
14 instance of |RCE|:
14 instance of |RCE|:
15 ``Apache HTTP Server`` and ``mod_dav_svn``.
15 ``Apache HTTP Server`` and ``mod_dav_svn``.
16
16
17
17
18 .. tip::
18 .. tip::
19
19
20 We recommend using Wandisco repositories which provide latest SVN versions
20 We recommend using Wandisco repositories which provide latest SVN versions
21 for most platforms. If you skip this version you'll have to ensure the Client version
21 for most platforms. If you skip this version you'll have to ensure the Client version
22 is compatible with installed SVN version which might differ depending on the operating system.
22 is compatible with installed SVN version which might differ depending on the operating system.
23 Here is an example how to add the Wandisco repositories for Ubuntu.
23 Here is an example how to add the Wandisco repositories for Ubuntu.
24
24
25 .. code-block:: bash
25 .. code-block:: bash
26
26
27 $ sudo sh -c 'echo "deb http://opensource.wandisco.com/ubuntu `lsb_release -cs` svn110" >> /etc/apt/sources.list.d/subversion110.list'
27 $ sudo sh -c 'echo "deb http://opensource.wandisco.com/ubuntu `lsb_release -cs` svn110" >> /etc/apt/sources.list.d/subversion110.list'
28 $ sudo wget -q http://opensource.wandisco.com/wandisco-debian-new.gpg -O- | sudo apt-key add -
28 $ sudo wget -q http://opensource.wandisco.com/wandisco-debian-new.gpg -O- | sudo apt-key add -
29 $ sudo apt-get update
29 $ sudo apt-get update
30
30
31 Here is an example how to add the Wandisco repositories for Centos/Redhat. Using
31 Here is an example how to add the Wandisco repositories for Centos/Redhat. Using
32 a yum config
32 a yum config
33
33
34 .. code-block:: bash
34 .. code-block:: bash
35
35
36 [wandisco-Git]
36 [wandisco-Git]
37 name=CentOS-6 - Wandisco Git
37 name=CentOS-6 - Wandisco Git
38 baseurl=http://opensource.wandisco.com/centos/6/git/$basearch/
38 baseurl=http://opensource.wandisco.com/centos/6/git/$basearch/
39 enabled=1
39 enabled=1
40 gpgcheck=1
40 gpgcheck=1
41 gpgkey=http://opensource.wandisco.com/RPM-GPG-KEY-WANdisco
41 gpgkey=http://opensource.wandisco.com/RPM-GPG-KEY-WANdisco
42
42
43
43
44
44
45 Example installation of required components for Ubuntu platform:
45 Example installation of required components for Ubuntu platform:
46
46
47 .. code-block:: bash
47 .. code-block:: bash
48
48
49 $ sudo apt-get install apache2
49 $ sudo apt-get install apache2
50 $ sudo apt-get install libapache2-svn
50 $ sudo apt-get install libapache2-svn
51
51
52 Once installed you need to enable ``dav_svn`` on Ubuntu:
52 Once installed you need to enable ``dav_svn`` on Ubuntu:
53
53
54 .. code-block:: bash
54 .. code-block:: bash
55
55
56 $ sudo a2enmod dav_svn
56 $ sudo a2enmod dav_svn
57 $ sudo a2enmod headers
57 $ sudo a2enmod headers
58 $ sudo a2enmod authn_anon
58 $ sudo a2enmod authn_anon
59
59
60
60
61 Example installation of required components for RedHat/CentOS platform:
61 Example installation of required components for RedHat/CentOS platform:
62
62
63 .. code-block:: bash
63 .. code-block:: bash
64
64
65 $ sudo yum install httpd
65 $ sudo yum install httpd
66 $ sudo yum install subversion mod_dav_svn
66 $ sudo yum install subversion mod_dav_svn
67
67
68
68
69 Once installed you need to enable ``dav_svn`` on RedHat/CentOS:
69 Once installed you need to enable ``dav_svn`` on RedHat/CentOS:
70
70
71 .. code-block:: bash
71 .. code-block:: bash
72
72
73 sudo vi /etc/httpd/conf.modules.d/10-subversion.conf
73 sudo vi /etc/httpd/conf.modules.d/10-subversion.conf
74 ## The file should read:
74 ## The file should read:
75
75
76 LoadModule dav_svn_module modules/mod_dav_svn.so
76 LoadModule dav_svn_module modules/mod_dav_svn.so
77 LoadModule headers_module modules/mod_headers.so
77 LoadModule headers_module modules/mod_headers.so
78 LoadModule authn_anon_module modules/mod_authn_anon.so
78 LoadModule authn_anon_module modules/mod_authn_anon.so
79
79
80 .. tip::
80 .. tip::
81
81
82 To check the installed mod_dav_svn module version, you can use such command.
82 To check the installed mod_dav_svn module version, you can use such command.
83
83
84 `strings /usr/lib/apache2/modules/mod_dav_svn.so | grep 'Powered by'`
84 `strings /usr/lib/apache2/modules/mod_dav_svn.so | grep 'Powered by'`
85
85
86
86
87 Configuring Apache Setup
87 Configuring Apache Setup
88 ========================
88 ========================
89
89
90 .. tip::
90 .. tip::
91
91
92 It is recommended to run Apache on a port other than 80, due to possible
92 It is recommended to run Apache on a port other than 80, due to possible
93 conflicts with other HTTP servers like nginx. To do this, set the
93 conflicts with other HTTP servers like nginx. To do this, set the
94 ``Listen`` parameter in the ``/etc/apache2/ports.conf`` file, for example
94 ``Listen`` parameter in the ``/etc/apache2/ports.conf`` file, for example
95 ``Listen 8090``.
95 ``Listen 8090``.
96
96
97
97
98 .. warning::
98 .. warning::
99
99
100 Make sure your Apache instance which runs the mod_dav_svn module is
100 Make sure your Apache instance which runs the mod_dav_svn module is
101 only accessible by |RCE|. Otherwise everyone is able to browse
101 only accessible by |RCE|. Otherwise everyone is able to browse
102 the repositories or run subversion operations (checkout/commit/etc.).
102 the repositories or run subversion operations (checkout/commit/etc.).
103
103
104 It is also recommended to run apache as the same user as |RCE|, otherwise
104 It is also recommended to run apache as the same user as |RCE|, otherwise
105 permission issues could occur. To do this edit the ``/etc/apache2/envvars``
105 permission issues could occur. To do this edit the ``/etc/apache2/envvars``
106
106
107 .. code-block:: apache
107 .. code-block:: apache
108
108
109 export APACHE_RUN_USER=rhodecode
109 export APACHE_RUN_USER=rhodecode
110 export APACHE_RUN_GROUP=rhodecode
110 export APACHE_RUN_GROUP=rhodecode
111
111
112 1. To configure Apache, create and edit a virtual hosts file, for example
112 1. To configure Apache, create and edit a virtual hosts file, for example
113 :file:`/etc/apache2/sites-enabled/default.conf`. Below is an example
113 :file:`/etc/apache2/sites-enabled/default.conf`. Below is an example
114 how to use one with auto-generated config ```mod_dav_svn.conf```
114 how to use one with auto-generated config ```mod_dav_svn.conf```
115 from configured |RCE| instance.
115 from configured |RCE| instance.
116
116
117 .. code-block:: apache
117 .. code-block:: apache
118
118
119 <VirtualHost *:8090>
119 <VirtualHost *:8090>
120 ServerAdmin rhodecode-admin@localhost
120 ServerAdmin rhodecode-admin@localhost
121 DocumentRoot /var/www/html
121 DocumentRoot /var/www/html
122 ErrorLog ${'${APACHE_LOG_DIR}'}/error.log
122 ErrorLog ${'${APACHE_LOG_DIR}'}/error.log
123 CustomLog ${'${APACHE_LOG_DIR}'}/access.log combined
123 CustomLog ${'${APACHE_LOG_DIR}'}/access.log combined
124 LogLevel info
124 LogLevel info
125 # allows custom host names, prevents 400 errors on checkout
125 # allows custom host names, prevents 400 errors on checkout
126 HttpProtocolOptions Unsafe
126 HttpProtocolOptions Unsafe
127 # Most likely this will be: /home/user/.rccontrol/enterprise-1/mod_dav_svn.conf
127 # Most likely this will be: /home/user/.rccontrol/enterprise-1/mod_dav_svn.conf
128 Include /home/user/.rccontrol/enterprise-1/mod_dav_svn.conf
128 Include /home/user/.rccontrol/enterprise-1/mod_dav_svn.conf
129 </VirtualHost>
129 </VirtualHost>
130
130
131
131
132 2. Go to the :menuselection:`Admin --> Settings --> VCS` page, and
132 2. Go to the :menuselection:`Admin --> Settings --> VCS` page, and
133 enable :guilabel:`Proxy Subversion HTTP requests`, and specify the
133 enable :guilabel:`Proxy Subversion HTTP requests`, and specify the
134 :guilabel:`Subversion HTTP Server URL`.
134 :guilabel:`Subversion HTTP Server URL`.
135
135
136 3. Open the |RCE| configuration file,
136 3. Open the |RCE| configuration file,
137 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
137 :file:`config/_shared/rhodecode.ini`
138
138
139 4. Add the following configuration option in the ``[app:main]``
139 4. Add the following configuration option in the ``[app:main]``
140 section if you don't have it yet.
140 section if you don't have it yet.
141
141
142 This enables mapping of the created |RCE| repo groups into special
142 This enables mapping of the created |RCE| repo groups into special
143 |svn| paths. Each time a new repository group is created, the system will
143 |svn| paths. Each time a new repository group is created, the system will
144 update the template file and create new mapping. Apache web server needs to
144 update the template file and create new mapping. Apache web server needs to
145 be reloaded to pick up the changes on this file.
145 be reloaded to pick up the changes on this file.
146 To do this, simply configure `svn.proxy.reload_cmd` inside the .ini file.
146 To do this, simply configure `svn.proxy.reload_cmd` inside the .ini file.
147 Example configuration:
147 Example configuration:
148
148
149
149
150 .. code-block:: ini
150 .. code-block:: ini
151
151
152 ############################################################
152 ############################################################
153 ### Subversion proxy support (mod_dav_svn) ###
153 ### Subversion proxy support (mod_dav_svn) ###
154 ### Maps RhodeCode repo groups into SVN paths for Apache ###
154 ### Maps RhodeCode repo groups into SVN paths for Apache ###
155 ############################################################
155 ############################################################
156 ## Enable or disable the config file generation.
156 ## Enable or disable the config file generation.
157 svn.proxy.generate_config = true
157 svn.proxy.generate_config = true
158 ## Generate config file with `SVNListParentPath` set to `On`.
158 ## Generate config file with `SVNListParentPath` set to `On`.
159 svn.proxy.list_parent_path = true
159 svn.proxy.list_parent_path = true
160 ## Set location and file name of generated config file.
160 ## Set location and file name of generated config file.
161 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
161 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
162 ## Used as a prefix to the <Location> block in the generated config file.
162 ## Used as a prefix to the <Location> block in the generated config file.
163 ## In most cases it should be set to `/`.
163 ## In most cases it should be set to `/`.
164 svn.proxy.location_root = /
164 svn.proxy.location_root = /
165 ## Command to reload the mod dav svn configuration on change.
165 ## Command to reload the mod dav svn configuration on change.
166 ## Example: `/etc/init.d/apache2 reload`
166 ## Example: `/etc/init.d/apache2 reload`
167 svn.proxy.reload_cmd = /etc/init.d/apache2 reload
167 svn.proxy.reload_cmd = /etc/init.d/apache2 reload
168 ## If the timeout expires before the reload command finishes, the command will
168 ## If the timeout expires before the reload command finishes, the command will
169 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
169 ## be killed. Setting it to zero means no timeout. Defaults to 10 seconds.
170 #svn.proxy.reload_timeout = 10
170 #svn.proxy.reload_timeout = 10
171
171
172
172
173 This would create a special template file called ```mod_dav_svn.conf```. We
173 This would create a special template file called ```mod_dav_svn.conf```. We
174 used that file path in the apache config above inside the Include statement.
174 used that file path in the apache config above inside the Include statement.
175 It's also possible to manually generate the config from the
175 It's also possible to manually generate the config from the
176 :menuselection:`Admin --> Settings --> VCS` page by clicking a
176 :menuselection:`Admin --> Settings --> VCS` page by clicking a
177 `Generate Apache Config` button.
177 `Generate Apache Config` button.
178
178
179 5. Now only things left is to enable svn support, and generate the initial
179 5. Now only things left is to enable svn support, and generate the initial
180 configuration.
180 configuration.
181
181
182 - Select `Proxy subversion HTTP requests` checkbox
182 - Select `Proxy subversion HTTP requests` checkbox
183 - Enter http://localhost:8090 into `Subversion HTTP Server URL`
183 - Enter http://localhost:8090 into `Subversion HTTP Server URL`
184 - Click the `Generate Apache Config` button.
184 - Click the `Generate Apache Config` button.
185
185
186 This config will be automatically re-generated once an user-groups is added
186 This config will be automatically re-generated once an user-groups is added
187 to properly map the additional paths generated.
187 to properly map the additional paths generated.
188
188
189
189
190
190
191 Using |svn|
191 Using |svn|
192 ===========
192 ===========
193
193
194 Once |svn| has been enabled on your instance, you can use it with the
194 Once |svn| has been enabled on your instance, you can use it with the
195 following examples. For more |svn| information, see the `Subversion Red Book`_
195 following examples. For more |svn| information, see the `Subversion Red Book`_
196
196
197 .. code-block:: bash
197 .. code-block:: bash
198
198
199 # To clone a repository
199 # To clone a repository
200 svn checkout http://my-svn-server.example.com/my-svn-repo
200 svn checkout http://my-svn-server.example.com/my-svn-repo
201
201
202 # svn commit
202 # svn commit
203 svn commit
203 svn commit
204
204
205
205
206 .. _Subversion Red Book: http://svnbook.red-bean.com/en/1.7/svn-book.html#svn.ref.svn
206 .. _Subversion Red Book: http://svnbook.red-bean.com/en/1.7/svn-book.html#svn.ref.svn
207
207
208 .. _Ask Ubuntu: http://askubuntu.com/questions/162391/how-do-i-fix-my-locale-issue No newline at end of file
208 .. _Ask Ubuntu: http://askubuntu.com/questions/162391/how-do-i-fix-my-locale-issue
@@ -1,22 +1,22 b''
1 .. _change-encoding:
1 .. _change-encoding:
2
2
3 Change Default Encoding
3 Change Default Encoding
4 -----------------------
4 -----------------------
5
5
6 |RCE| uses ``utf8`` encoding by default. You can change the default encoding
6 |RCE| uses ``utf8`` encoding by default. You can change the default encoding
7 in the :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file. To
7 in the :file:`config/_shared/rhodecode.ini` file. To
8 change the default encoding used by |RCE|, set a new value for the
8 change the default encoding used by |RCE|, set a new value for the
9 ``default_encoding``.
9 ``default_encoding``.
10
10
11 .. code-block:: ini
11 .. code-block:: ini
12
12
13 # default encoding used to convert from and to unicode
13 # default encoding used to convert from and to unicode
14 # can be also a comma separated list of encoding in case of mixed
14 # can be also a comma separated list of encoding in case of mixed
15 # encodings
15 # encodings
16 default_encoding = utf8
16 default_encoding = utf8
17
17
18 .. note::
18 .. note::
19
19
20 Changing the default encoding will affect many parts of your |RCE|
20 Changing the default encoding will affect many parts of your |RCE|
21 installation, including committers names,
21 installation, including committers names,
22 file names, and the encoding of commit messages.
22 file names, and the encoding of commit messages.
@@ -1,17 +1,17 b''
1 .. _hg-auth-loop:
1 .. _hg-auth-loop:
2
2
3 |hg| Authentication Tuning
3 |hg| Authentication Tuning
4 --------------------------
4 --------------------------
5
5
6 When using external authentication tools such as LDAP with |hg|, a
6 When using external authentication tools such as LDAP with |hg|, a
7 password retry loop in |hg| can result in users being locked out due to too
7 password retry loop in |hg| can result in users being locked out due to too
8 many failed password attempts. To prevent this from happening, add the
8 many failed password attempts. To prevent this from happening, add the
9 following setting to your
9 following setting to your
10 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file, in the
10 :file:`config/_shared/rhodecode.ini` file, in the
11 ``[app:main]`` section.
11 ``[app:main]`` section.
12
12
13
13
14 .. code-block:: ini
14 .. code-block:: ini
15
15
16 [app:main]
16 [app:main]
17 auth_ret_code_detection = true
17 auth_ret_code_detection = true
@@ -1,396 +1,396 b''
1 .. _scale-horizontal-cluster:
1 .. _scale-horizontal-cluster:
2
2
3
3
4 Scale Horizontally / RhodeCode Cluster
4 Scale Horizontally / RhodeCode Cluster
5 --------------------------------------
5 --------------------------------------
6
6
7 |RCE| is built in a way it support horizontal scaling across multiple machines.
7 |RCE| is built in a way it support horizontal scaling across multiple machines.
8 There are three main pre-requisites for that:
8 There are three main pre-requisites for that:
9
9
10 - Shared storage that each machine can access. Using NFS or other shared storage system.
10 - Shared storage that each machine can access. Using NFS or other shared storage system.
11 - Shared DB connection across machines. Using `MySQL`/`PostgreSQL` that each node can access.
11 - Shared DB connection across machines. Using `MySQL`/`PostgreSQL` that each node can access.
12 - |RCE| user sessions and caches need to use a shared storage (e.g `Redis`_/`Memcached`)
12 - |RCE| user sessions and caches need to use a shared storage (e.g `Redis`_/`Memcached`)
13
13
14
14
15 Horizontal scaling means adding more machines or workers into your pool of
15 Horizontal scaling means adding more machines or workers into your pool of
16 resources. Horizontally scaling |RCE| gives a huge performance increase,
16 resources. Horizontally scaling |RCE| gives a huge performance increase,
17 especially under large traffic scenarios with a high number of requests.
17 especially under large traffic scenarios with a high number of requests.
18 This is very beneficial when |RCE| is serving many users simultaneously,
18 This is very beneficial when |RCE| is serving many users simultaneously,
19 or if continuous integration servers are automatically pulling and pushing code.
19 or if continuous integration servers are automatically pulling and pushing code.
20 It also adds High-Availability to your running system.
20 It also adds High-Availability to your running system.
21
21
22
22
23 Cluster Overview
23 Cluster Overview
24 ^^^^^^^^^^^^^^^^
24 ^^^^^^^^^^^^^^^^
25
25
26 Below we'll present a configuration example that will use two separate nodes to serve
26 Below we'll present a configuration example that will use two separate nodes to serve
27 |RCE| in a load-balanced environment. The 3rd node will act as a shared storage/cache
27 |RCE| in a load-balanced environment. The 3rd node will act as a shared storage/cache
28 and handle load-balancing. In addition 3rd node will be used as shared database instance.
28 and handle load-balancing. In addition 3rd node will be used as shared database instance.
29
29
30 This setup can be used both in Docker based configuration or with individual
30 This setup can be used both in Docker based configuration or with individual
31 physical/virtual machines. Using the 3rd node for Storage/Redis/PostgreSQL/Nginx is
31 physical/virtual machines. Using the 3rd node for Storage/Redis/PostgreSQL/Nginx is
32 optional. All those components can be installed on one of the two nodes used for |RCE|.
32 optional. All those components can be installed on one of the two nodes used for |RCE|.
33 We'll use following naming for our nodes:
33 We'll use following naming for our nodes:
34
34
35 - `rc-node-1` (NFS, DB, Cache node)
35 - `rc-node-1` (NFS, DB, Cache node)
36 - `rc-node-2` (Worker node1)
36 - `rc-node-2` (Worker node1)
37 - `rc-node-3` (Worker node2)
37 - `rc-node-3` (Worker node2)
38
38
39 Our shares NFS storage in the example is located on `/home/rcdev/storage` and
39 Our shares NFS storage in the example is located on `/home/rcdev/storage` and
40 it's RW accessible on **each** node.
40 it's RW accessible on **each** node.
41
41
42 In this example we used certain recommended components, however many
42 In this example we used certain recommended components, however many
43 of those can be replaced by other, in case your organization already uses them, for example:
43 of those can be replaced by other, in case your organization already uses them, for example:
44
44
45 - `MySQL`/`PostgreSQL`: Aren't replaceable and are the two only supported databases.
45 - `MySQL`/`PostgreSQL`: Aren't replaceable and are the two only supported databases.
46 - `Nginx`_ on `rc-node-1` can be replaced by: `Hardware Load Balancer (F5)`, `Apache`_, `HA-Proxy` etc.
46 - `Nginx`_ on `rc-node-1` can be replaced by: `Hardware Load Balancer (F5)`, `Apache`_, `HA-Proxy` etc.
47 - `Nginx`_ on rc-node-2/3 acts as a reverse proxy and can be replaced by other HTTP server
47 - `Nginx`_ on rc-node-2/3 acts as a reverse proxy and can be replaced by other HTTP server
48 acting as reverse proxy such as `Apache`_.
48 acting as reverse proxy such as `Apache`_.
49 - `Redis`_ on `rc-node-1` can be replaced by: `Memcached`
49 - `Redis`_ on `rc-node-1` can be replaced by: `Memcached`
50
50
51
51
52 Here's an overview what components should be installed/setup on each server in our example:
52 Here's an overview what components should be installed/setup on each server in our example:
53
53
54 - **rc-node-1**:
54 - **rc-node-1**:
55
55
56 - main storage acting as NFS host.
56 - main storage acting as NFS host.
57 - `nginx` acting as a load-balancer.
57 - `nginx` acting as a load-balancer.
58 - `postgresql-server` used for database and sessions.
58 - `postgresql-server` used for database and sessions.
59 - `redis-server` used for storing shared caches.
59 - `redis-server` used for storing shared caches.
60 - optionally `rabbitmq-server` or `redis` for `Celery` if used.
60 - optionally `rabbitmq-server` or `redis` for `Celery` if used.
61 - optionally if `Celery` is used Enterprise/Community instance + VCSServer.
61 - optionally if `Celery` is used Enterprise/Community instance + VCSServer.
62 - optionally mailserver that can be shared by other instances.
62 - optionally mailserver that can be shared by other instances.
63 - optionally channelstream server to handle live communication for all instances.
63 - optionally channelstream server to handle live communication for all instances.
64
64
65
65
66 - **rc-node-2/3**:
66 - **rc-node-2/3**:
67
67
68 - `nginx` acting as a reverse proxy to handle requests to |RCE|.
68 - `nginx` acting as a reverse proxy to handle requests to |RCE|.
69 - 1x RhodeCode Enterprise/Community instance.
69 - 1x RhodeCode Enterprise/Community instance.
70 - 1x VCSServer instance.
70 - 1x VCSServer instance.
71 - optionally for testing connection: postgresql-client, redis-client (redis-tools).
71 - optionally for testing connection: postgresql-client, redis-client (redis-tools).
72
72
73
73
74 Before we start here are few assumptions that should be fulfilled:
74 Before we start here are few assumptions that should be fulfilled:
75
75
76 - make sure each node can access each other.
76 - make sure each node can access each other.
77 - make sure `Redis`_/`MySQL`/`PostgreSQL`/`RabbitMQ`_ are running on `rc-node-1`
77 - make sure `Redis`_/`MySQL`/`PostgreSQL`/`RabbitMQ`_ are running on `rc-node-1`
78 - make sure both `rc-node-2`/`3` can access NFS storage with RW access
78 - make sure both `rc-node-2`/`3` can access NFS storage with RW access
79 - make sure rc-node-2/3 can access `Redis`_/`PostgreSQL`, `MySQL` database on `rc-node-1`.
79 - make sure rc-node-2/3 can access `Redis`_/`PostgreSQL`, `MySQL` database on `rc-node-1`.
80 - make sure `Redis`_/Database/`RabbitMQ`_ are password protected and accessible only from rc-node-2/3.
80 - make sure `Redis`_/Database/`RabbitMQ`_ are password protected and accessible only from rc-node-2/3.
81
81
82
82
83
83
84 Setup rc-node-2/3
84 Setup rc-node-2/3
85 ^^^^^^^^^^^^^^^^^
85 ^^^^^^^^^^^^^^^^^
86
86
87 Initially before `rc-node-1` we'll configure both nodes 2 and 3 to operate as standalone
87 Initially before `rc-node-1` we'll configure both nodes 2 and 3 to operate as standalone
88 nodes with their own hostnames. Use a default installation settings, and use
88 nodes with their own hostnames. Use a default installation settings, and use
89 the default local addresses (127.0.0.1) to configure VCSServer and Community/Enterprise instances.
89 the default local addresses (127.0.0.1) to configure VCSServer and Community/Enterprise instances.
90 All external connectivity will be handled by the reverse proxy (`Nginx`_ in our example).
90 All external connectivity will be handled by the reverse proxy (`Nginx`_ in our example).
91
91
92 This way we can ensure each individual host works,
92 This way we can ensure each individual host works,
93 accepts connections, or do some operations explicitly on chosen node.
93 accepts connections, or do some operations explicitly on chosen node.
94
94
95 In addition this would allow use to explicitly direct certain traffic to a node, e.g
95 In addition this would allow use to explicitly direct certain traffic to a node, e.g
96 CI server will only call directly `rc-node-3`. This should be done similar to normal
96 CI server will only call directly `rc-node-3`. This should be done similar to normal
97 installation so check out `Nginx`_/`Apache`_ configuration example to configure each host.
97 installation so check out `Nginx`_/`Apache`_ configuration example to configure each host.
98 Each one should already connect to shared database during installation.
98 Each one should already connect to shared database during installation.
99
99
100
100
101 1) Assuming our final url will be http://rc-node-1, Configure `instances_id`, `app.base_url`
101 1) Assuming our final url will be http://rc-node-1, Configure `instances_id`, `app.base_url`
102
102
103 a) On **rc-node-2** find the following settings and edit :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
103 a) On **rc-node-2** find the following settings and edit :file:`config/_shared/rhodecode.ini`
104
104
105 .. code-block:: ini
105 .. code-block:: ini
106
106
107 ## required format is: *NAME-
107 ## required format is: *NAME-
108 instance_id = *rc-node-2-
108 instance_id = *rc-node-2-
109 app.base_url = http://rc-node-1
109 app.base_url = http://rc-node-1
110
110
111
111
112 b) On **rc-node-3** find the following settings and edit :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
112 b) On **rc-node-3** find the following settings and edit :file:`config/_shared/rhodecode.ini`
113
113
114 .. code-block:: ini
114 .. code-block:: ini
115
115
116 ## required format is: *NAME-
116 ## required format is: *NAME-
117 instance_id = *rc-node-3-
117 instance_id = *rc-node-3-
118 app.base_url = http://rc-node-1
118 app.base_url = http://rc-node-1
119
119
120
120
121
121
122 2) Configure `User Session` to use a shared database. Example config that should be
122 2) Configure `User Session` to use a shared database. Example config that should be
123 changed on both **rc-node-2** and **rc-node-3** .
123 changed on both **rc-node-2** and **rc-node-3** .
124 Edit :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
124 Edit :file:`config/_shared/rhodecode.ini`
125
125
126 .. code-block:: ini
126 .. code-block:: ini
127
127
128 ####################################
128 ####################################
129 ### BEAKER SESSION ####
129 ### BEAKER SESSION ####
130 ####################################
130 ####################################
131
131
132 ## Disable the default `file` sessions
132 ## Disable the default `file` sessions
133 #beaker.session.type = file
133 #beaker.session.type = file
134 #beaker.session.data_dir = %(here)s/data/sessions
134 #beaker.session.data_dir = %(here)s/data/sessions
135
135
136 ## use shared db based session, fast, and allows easy management over logged in users
136 ## use shared db based session, fast, and allows easy management over logged in users
137 beaker.session.type = ext:database
137 beaker.session.type = ext:database
138 beaker.session.table_name = db_session
138 beaker.session.table_name = db_session
139 # use our rc-node-1 here
139 # use our rc-node-1 here
140 beaker.session.sa.url = postgresql://postgres:qweqwe@rc-node-1/rhodecode
140 beaker.session.sa.url = postgresql://postgres:qweqwe@rc-node-1/rhodecode
141 beaker.session.sa.pool_recycle = 3600
141 beaker.session.sa.pool_recycle = 3600
142 beaker.session.sa.echo = false
142 beaker.session.sa.echo = false
143
143
144 In addition make sure both instances use the same `session.secret` so users have
144 In addition make sure both instances use the same `session.secret` so users have
145 persistent sessions across nodes. Please generate other one then in this example.
145 persistent sessions across nodes. Please generate other one then in this example.
146
146
147 .. code-block:: ini
147 .. code-block:: ini
148
148
149 # use a unique generated long string
149 # use a unique generated long string
150 beaker.session.secret = 70e116cae2274656ba7265fd860aebbd
150 beaker.session.secret = 70e116cae2274656ba7265fd860aebbd
151
151
152 3) Configure stored cached/archive cache to our shared NFS `rc-node-1`
152 3) Configure stored cached/archive cache to our shared NFS `rc-node-1`
153
153
154 .. code-block:: ini
154 .. code-block:: ini
155
155
156 # note the `_` prefix that allows using a directory without
156 # note the `_` prefix that allows using a directory without
157 # remap and rescan checking for vcs inside it.
157 # remap and rescan checking for vcs inside it.
158 cache_dir = /home/rcdev/storage/_cache_dir/data
158 cache_dir = /home/rcdev/storage/_cache_dir/data
159 # note archive cache dir is disabled by default, however if you enable
159 # note archive cache dir is disabled by default, however if you enable
160 # it also needs to be shared
160 # it also needs to be shared
161 #archive_cache_dir = /home/rcdev/storage/_tarball_cache_dir
161 #archive_cache_dir = /home/rcdev/storage/_tarball_cache_dir
162
162
163
163
164 4) Use shared exception store. Example config that should be
164 4) Use shared exception store. Example config that should be
165 changed on both **rc-node-2** and **rc-node-3**, and also for VCSServer.
165 changed on both **rc-node-2** and **rc-node-3**, and also for VCSServer.
166 Edit :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` and
166 Edit :file:`config/_shared/rhodecode.ini` and
167 :file:`/home/{user}/.rccontrol/{vcsserver-instance-id}/vcsserver.ini`
167 :file:`/home/{user}/.rccontrol/{vcsserver-instance-id}/vcsserver.ini`
168 and add/change following setting.
168 and add/change following setting.
169
169
170 .. code-block:: ini
170 .. code-block:: ini
171
171
172 exception_tracker.store_path = /home/rcdev/storage/_exception_store_data
172 exception_tracker.store_path = /home/rcdev/storage/_exception_store_data
173
173
174
174
175 5) Change cache backends to use `Redis`_ based caches. Below full example config
175 5) Change cache backends to use `Redis`_ based caches. Below full example config
176 that replaces default file-based cache to shared `Redis`_ with Distributed Lock.
176 that replaces default file-based cache to shared `Redis`_ with Distributed Lock.
177
177
178
178
179 .. code-block:: ini
179 .. code-block:: ini
180
180
181 #####################################
181 #####################################
182 ### DOGPILE CACHE ####
182 ### DOGPILE CACHE ####
183 #####################################
183 #####################################
184
184
185 ## `cache_perms` cache settings for permission tree, auth TTL.
185 ## `cache_perms` cache settings for permission tree, auth TTL.
186 #rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace
186 #rc_cache.cache_perms.backend = dogpile.cache.rc.file_namespace
187 #rc_cache.cache_perms.expiration_time = 300
187 #rc_cache.cache_perms.expiration_time = 300
188
188
189 ## alternative `cache_perms` redis backend with distributed lock
189 ## alternative `cache_perms` redis backend with distributed lock
190 rc_cache.cache_perms.backend = dogpile.cache.rc.redis
190 rc_cache.cache_perms.backend = dogpile.cache.rc.redis
191 rc_cache.cache_perms.expiration_time = 300
191 rc_cache.cache_perms.expiration_time = 300
192 ## redis_expiration_time needs to be greater then expiration_time
192 ## redis_expiration_time needs to be greater then expiration_time
193 rc_cache.cache_perms.arguments.redis_expiration_time = 7200
193 rc_cache.cache_perms.arguments.redis_expiration_time = 7200
194 rc_cache.cache_perms.arguments.socket_timeout = 30
194 rc_cache.cache_perms.arguments.socket_timeout = 30
195 rc_cache.cache_perms.arguments.host = rc-node-1
195 rc_cache.cache_perms.arguments.host = rc-node-1
196 rc_cache.cache_perms.arguments.password = qweqwe
196 rc_cache.cache_perms.arguments.password = qweqwe
197 rc_cache.cache_perms.arguments.port = 6379
197 rc_cache.cache_perms.arguments.port = 6379
198 rc_cache.cache_perms.arguments.db = 0
198 rc_cache.cache_perms.arguments.db = 0
199 rc_cache.cache_perms.arguments.distributed_lock = true
199 rc_cache.cache_perms.arguments.distributed_lock = true
200
200
201 ## `cache_repo` cache settings for FileTree, Readme, RSS FEEDS
201 ## `cache_repo` cache settings for FileTree, Readme, RSS FEEDS
202 #rc_cache.cache_repo.backend = dogpile.cache.rc.file_namespace
202 #rc_cache.cache_repo.backend = dogpile.cache.rc.file_namespace
203 #rc_cache.cache_repo.expiration_time = 2592000
203 #rc_cache.cache_repo.expiration_time = 2592000
204
204
205 ## alternative `cache_repo` redis backend with distributed lock
205 ## alternative `cache_repo` redis backend with distributed lock
206 rc_cache.cache_repo.backend = dogpile.cache.rc.redis
206 rc_cache.cache_repo.backend = dogpile.cache.rc.redis
207 rc_cache.cache_repo.expiration_time = 2592000
207 rc_cache.cache_repo.expiration_time = 2592000
208 ## redis_expiration_time needs to be greater then expiration_time
208 ## redis_expiration_time needs to be greater then expiration_time
209 rc_cache.cache_repo.arguments.redis_expiration_time = 2678400
209 rc_cache.cache_repo.arguments.redis_expiration_time = 2678400
210 rc_cache.cache_repo.arguments.socket_timeout = 30
210 rc_cache.cache_repo.arguments.socket_timeout = 30
211 rc_cache.cache_repo.arguments.host = rc-node-1
211 rc_cache.cache_repo.arguments.host = rc-node-1
212 rc_cache.cache_repo.arguments.password = qweqwe
212 rc_cache.cache_repo.arguments.password = qweqwe
213 rc_cache.cache_repo.arguments.port = 6379
213 rc_cache.cache_repo.arguments.port = 6379
214 rc_cache.cache_repo.arguments.db = 1
214 rc_cache.cache_repo.arguments.db = 1
215 rc_cache.cache_repo.arguments.distributed_lock = true
215 rc_cache.cache_repo.arguments.distributed_lock = true
216
216
217 ## cache settings for SQL queries, this needs to use memory type backend
217 ## cache settings for SQL queries, this needs to use memory type backend
218 rc_cache.sql_cache_short.backend = dogpile.cache.rc.memory_lru
218 rc_cache.sql_cache_short.backend = dogpile.cache.rc.memory_lru
219 rc_cache.sql_cache_short.expiration_time = 30
219 rc_cache.sql_cache_short.expiration_time = 30
220
220
221 ## `cache_repo_longterm` cache for repo object instances, this needs to use memory
221 ## `cache_repo_longterm` cache for repo object instances, this needs to use memory
222 ## type backend as the objects kept are not pickle serializable
222 ## type backend as the objects kept are not pickle serializable
223 rc_cache.cache_repo_longterm.backend = dogpile.cache.rc.memory_lru
223 rc_cache.cache_repo_longterm.backend = dogpile.cache.rc.memory_lru
224 ## by default we use 96H, this is using invalidation on push anyway
224 ## by default we use 96H, this is using invalidation on push anyway
225 rc_cache.cache_repo_longterm.expiration_time = 345600
225 rc_cache.cache_repo_longterm.expiration_time = 345600
226 ## max items in LRU cache, reduce this number to save memory, and expire last used
226 ## max items in LRU cache, reduce this number to save memory, and expire last used
227 ## cached objects
227 ## cached objects
228 rc_cache.cache_repo_longterm.max_size = 10000
228 rc_cache.cache_repo_longterm.max_size = 10000
229
229
230
230
231 6) Configure `Nginx`_ as reverse proxy on `rc-node-2/3`:
231 6) Configure `Nginx`_ as reverse proxy on `rc-node-2/3`:
232 Minimal `Nginx`_ config used:
232 Minimal `Nginx`_ config used:
233
233
234
234
235 .. code-block:: nginx
235 .. code-block:: nginx
236
236
237 ## rate limiter for certain pages to prevent brute force attacks
237 ## rate limiter for certain pages to prevent brute force attacks
238 limit_req_zone $binary_remote_addr zone=req_limit:10m rate=1r/s;
238 limit_req_zone $binary_remote_addr zone=req_limit:10m rate=1r/s;
239
239
240 ## custom log format
240 ## custom log format
241 log_format log_custom '$remote_addr - $remote_user [$time_local] '
241 log_format log_custom '$remote_addr - $remote_user [$time_local] '
242 '"$request" $status $body_bytes_sent '
242 '"$request" $status $body_bytes_sent '
243 '"$http_referer" "$http_user_agent" '
243 '"$http_referer" "$http_user_agent" '
244 '$request_time $upstream_response_time $pipe';
244 '$request_time $upstream_response_time $pipe';
245
245
246 server {
246 server {
247 listen 80;
247 listen 80;
248 server_name rc-node-2;
248 server_name rc-node-2;
249 #server_name rc-node-3;
249 #server_name rc-node-3;
250
250
251 access_log /var/log/nginx/rhodecode.access.log log_custom;
251 access_log /var/log/nginx/rhodecode.access.log log_custom;
252 error_log /var/log/nginx/rhodecode.error.log;
252 error_log /var/log/nginx/rhodecode.error.log;
253
253
254 # example of proxy.conf can be found in our docs.
254 # example of proxy.conf can be found in our docs.
255 include /etc/nginx/proxy.conf;
255 include /etc/nginx/proxy.conf;
256
256
257 ## serve static files by Nginx, recommended for performance
257 ## serve static files by Nginx, recommended for performance
258 location /_static/rhodecode {
258 location /_static/rhodecode {
259 gzip on;
259 gzip on;
260 gzip_min_length 500;
260 gzip_min_length 500;
261 gzip_proxied any;
261 gzip_proxied any;
262 gzip_comp_level 4;
262 gzip_comp_level 4;
263 gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/json application/xml application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
263 gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/json application/xml application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
264 gzip_vary on;
264 gzip_vary on;
265 gzip_disable "msie6";
265 gzip_disable "msie6";
266 expires 60d;
266 expires 60d;
267 #alias /home/rcdev/.rccontrol/community-1/static;
267 #alias /home/rcdev/.rccontrol/community-1/static;
268 alias /home/rcdev/.rccontrol/enterprise-1/static;
268 alias /home/rcdev/.rccontrol/enterprise-1/static;
269 }
269 }
270
270
271
271
272 location /_admin/login {
272 location /_admin/login {
273 limit_req zone=req_limit burst=10 nodelay;
273 limit_req zone=req_limit burst=10 nodelay;
274 try_files $uri @rhode;
274 try_files $uri @rhode;
275 }
275 }
276
276
277 location / {
277 location / {
278 try_files $uri @rhode;
278 try_files $uri @rhode;
279 }
279 }
280
280
281 location @rhode {
281 location @rhode {
282 # Url to running RhodeCode instance.
282 # Url to running RhodeCode instance.
283 # This is shown as `- URL: <host>` in output from rccontrol status.
283 # This is shown as `- URL: <host>` in output from rccontrol status.
284 proxy_pass http://127.0.0.1:10020;
284 proxy_pass http://127.0.0.1:10020;
285 }
285 }
286
286
287 ## custom 502 error page. Will be displayed while RhodeCode server
287 ## custom 502 error page. Will be displayed while RhodeCode server
288 ## is turned off
288 ## is turned off
289 error_page 502 /502.html;
289 error_page 502 /502.html;
290 location = /502.html {
290 location = /502.html {
291 #root /home/rcdev/.rccontrol/community-1/static;
291 #root /home/rcdev/.rccontrol/community-1/static;
292 root /home/rcdev/.rccontrol/enterprise-1/static;
292 root /home/rcdev/.rccontrol/enterprise-1/static;
293 }
293 }
294 }
294 }
295
295
296
296
297 7) Optional: Full text search, in case you use `Whoosh` full text search we also need a
297 7) Optional: Full text search, in case you use `Whoosh` full text search we also need a
298 shared storage for the index. In our example our NFS is mounted at `/home/rcdev/storage`
298 shared storage for the index. In our example our NFS is mounted at `/home/rcdev/storage`
299 which represents out storage so we can use the following:
299 which represents out storage so we can use the following:
300
300
301 .. code-block:: ini
301 .. code-block:: ini
302
302
303 # note the `_` prefix that allows using a directory without
303 # note the `_` prefix that allows using a directory without
304 # remap and rescan checking for vcs inside it.
304 # remap and rescan checking for vcs inside it.
305 search.location = /home/rcdev/storage/_index_data/index
305 search.location = /home/rcdev/storage/_index_data/index
306
306
307
307
308 .. note::
308 .. note::
309
309
310 If you use ElasticSearch it's by default shared, and simply running ES node is
310 If you use ElasticSearch it's by default shared, and simply running ES node is
311 by default cluster compatible.
311 by default cluster compatible.
312
312
313
313
314 8) Optional: If you intend to use mailing all instances need to use either a shared
314 8) Optional: If you intend to use mailing all instances need to use either a shared
315 mailing node, or each will use individual local mail agent. Simply put node-1/2/3
315 mailing node, or each will use individual local mail agent. Simply put node-1/2/3
316 needs to use same mailing configuration.
316 needs to use same mailing configuration.
317
317
318
318
319
319
320 Setup rc-node-1
320 Setup rc-node-1
321 ^^^^^^^^^^^^^^^
321 ^^^^^^^^^^^^^^^
322
322
323
323
324 Configure `Nginx`_ as Load Balancer to rc-node-2/3.
324 Configure `Nginx`_ as Load Balancer to rc-node-2/3.
325 Minimal `Nginx`_ example below:
325 Minimal `Nginx`_ example below:
326
326
327 .. code-block:: nginx
327 .. code-block:: nginx
328
328
329 ## define rc-cluster which contains a pool of our instances to connect to
329 ## define rc-cluster which contains a pool of our instances to connect to
330 upstream rc-cluster {
330 upstream rc-cluster {
331 # rc-node-2/3 are stored in /etc/hosts with correct IP addresses
331 # rc-node-2/3 are stored in /etc/hosts with correct IP addresses
332 server rc-node-2:80;
332 server rc-node-2:80;
333 server rc-node-3:80;
333 server rc-node-3:80;
334 }
334 }
335
335
336 server {
336 server {
337 listen 80;
337 listen 80;
338 server_name rc-node-1;
338 server_name rc-node-1;
339
339
340 location / {
340 location / {
341 proxy_pass http://rc-cluster;
341 proxy_pass http://rc-cluster;
342 }
342 }
343 }
343 }
344
344
345
345
346 .. note::
346 .. note::
347
347
348 You should configure your load balancing accordingly. We recommend writing
348 You should configure your load balancing accordingly. We recommend writing
349 load balancing rules that will separate regular user traffic from
349 load balancing rules that will separate regular user traffic from
350 automated process traffic like continuous servers or build bots. Sticky sessions
350 automated process traffic like continuous servers or build bots. Sticky sessions
351 are not required.
351 are not required.
352
352
353
353
354 Show which instance handles a request
354 Show which instance handles a request
355 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
355 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
356
356
357 You can easily check if load-balancing is working as expected. Visit our main node
357 You can easily check if load-balancing is working as expected. Visit our main node
358 `rc-node-1` URL which at that point should already handle incoming requests and balance
358 `rc-node-1` URL which at that point should already handle incoming requests and balance
359 it across node-2/3.
359 it across node-2/3.
360
360
361 Add a special GET param `?showrcid=1` to show current instance handling your request.
361 Add a special GET param `?showrcid=1` to show current instance handling your request.
362
362
363 For example: visiting url `http://rc-node-1/?showrcid=1` will show, in the bottom
363 For example: visiting url `http://rc-node-1/?showrcid=1` will show, in the bottom
364 of the screen` cluster instance info.
364 of the screen` cluster instance info.
365 e.g: `RhodeCode instance id: rc-node-3-rc-node-3-3246`
365 e.g: `RhodeCode instance id: rc-node-3-rc-node-3-3246`
366 which is generated from::
366 which is generated from::
367
367
368 <NODE_HOSTNAME>-<INSTANCE_ID>-<WORKER_PID>
368 <NODE_HOSTNAME>-<INSTANCE_ID>-<WORKER_PID>
369
369
370
370
371 Using Celery with cluster
371 Using Celery with cluster
372 ^^^^^^^^^^^^^^^^^^^^^^^^^
372 ^^^^^^^^^^^^^^^^^^^^^^^^^
373
373
374
374
375 If `Celery` is used we recommend setting also an instance of Enterprise/Community+VCSserver
375 If `Celery` is used we recommend setting also an instance of Enterprise/Community+VCSserver
376 on the node that is running `RabbitMQ`_ or `Redis`_. Those instances will be used to
376 on the node that is running `RabbitMQ`_ or `Redis`_. Those instances will be used to
377 executed async tasks on the `rc-node-1`. This is the most efficient setup.
377 executed async tasks on the `rc-node-1`. This is the most efficient setup.
378 `Celery` usually handles tasks such as sending emails, forking repositories, importing
378 `Celery` usually handles tasks such as sending emails, forking repositories, importing
379 repositories from external location etc. Using workers on instance that has
379 repositories from external location etc. Using workers on instance that has
380 the direct access to disks used by NFS as well as email server gives noticeable
380 the direct access to disks used by NFS as well as email server gives noticeable
381 performance boost. Running local workers to the NFS storage results in faster
381 performance boost. Running local workers to the NFS storage results in faster
382 execution of forking large repositories or sending lots of emails.
382 execution of forking large repositories or sending lots of emails.
383
383
384 Those instances need to be configured in the same way as for other nodes.
384 Those instances need to be configured in the same way as for other nodes.
385 The instance in rc-node-1 can be added to the cluster, but we don't recommend doing it.
385 The instance in rc-node-1 can be added to the cluster, but we don't recommend doing it.
386 For best results let it be isolated to only executing `Celery` tasks in the cluster setup.
386 For best results let it be isolated to only executing `Celery` tasks in the cluster setup.
387
387
388
388
389 .. _Gunicorn: http://gunicorn.org/
389 .. _Gunicorn: http://gunicorn.org/
390 .. _Whoosh: https://pypi.python.org/pypi/Whoosh/
390 .. _Whoosh: https://pypi.python.org/pypi/Whoosh/
391 .. _Elasticsearch: https://www.elastic.co/..
391 .. _Elasticsearch: https://www.elastic.co/..
392 .. _RabbitMQ: http://www.rabbitmq.com/
392 .. _RabbitMQ: http://www.rabbitmq.com/
393 .. _Nginx: http://nginx.io
393 .. _Nginx: http://nginx.io
394 .. _Apache: http://nginx.io
394 .. _Apache: http://nginx.io
395 .. _Redis: http://redis.io
395 .. _Redis: http://redis.io
396
396
@@ -1,67 +1,67 b''
1 .. _user-session-ref:
1 .. _user-session-ref:
2
2
3 User Session Performance
3 User Session Performance
4 ------------------------
4 ------------------------
5
5
6 The default file-based sessions are only suitable for smaller setups, or
6 The default file-based sessions are only suitable for smaller setups, or
7 instances that doesn't have a lot of users or traffic.
7 instances that doesn't have a lot of users or traffic.
8 They are set as default option because it's setup-free solution.
8 They are set as default option because it's setup-free solution.
9
9
10 The most common issue of file based sessions are file limit errors which occur
10 The most common issue of file based sessions are file limit errors which occur
11 if there are lots of session files.
11 if there are lots of session files.
12
12
13 Therefore, in a large scale deployment, to give better performance,
13 Therefore, in a large scale deployment, to give better performance,
14 scalability, and maintainability we recommend switching from file-based
14 scalability, and maintainability we recommend switching from file-based
15 sessions to database-based user sessions or Redis based sessions.
15 sessions to database-based user sessions or Redis based sessions.
16
16
17 To switch to database-based user sessions uncomment the following section in
17 To switch to database-based user sessions uncomment the following section in
18 your :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file.
18 your :file:`config/_shared/rhodecode.ini` file.
19
19
20
20
21 .. code-block:: ini
21 .. code-block:: ini
22
22
23 ## db based session, fast, and allows easy management over logged in users
23 ## db based session, fast, and allows easy management over logged in users
24 beaker.session.type = ext:database
24 beaker.session.type = ext:database
25 beaker.session.table_name = db_session
25 beaker.session.table_name = db_session
26
26
27 # use just one of the following according to the type of database
27 # use just one of the following according to the type of database
28 beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
28 beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
29 # or
29 # or
30 beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
30 beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
31
31
32 beaker.session.sa.pool_recycle = 3600
32 beaker.session.sa.pool_recycle = 3600
33 beaker.session.sa.echo = false
33 beaker.session.sa.echo = false
34
34
35
35
36 and make sure you comment out the file based sessions.
36 and make sure you comment out the file based sessions.
37
37
38 .. code-block:: ini
38 .. code-block:: ini
39
39
40 ## types are file, ext:memcached, ext:database, and memory (default).
40 ## types are file, ext:memcached, ext:database, and memory (default).
41 #beaker.session.type = file
41 #beaker.session.type = file
42 #beaker.session.data_dir = %(here)s/data/sessions/data
42 #beaker.session.data_dir = %(here)s/data/sessions/data
43
43
44
44
45 The `table_name` will be automatically created on specified database if it isn't yet existing.
45 The `table_name` will be automatically created on specified database if it isn't yet existing.
46 Database specified in the `beaker.session.sa.url` can be the same that RhodeCode
46 Database specified in the `beaker.session.sa.url` can be the same that RhodeCode
47 uses, or if required it can be a different one. We recommend to use the same database.
47 uses, or if required it can be a different one. We recommend to use the same database.
48
48
49
49
50
50
51 To switch to redis-based user sessions uncomment the following section in
51 To switch to redis-based user sessions uncomment the following section in
52 your :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file.
52 your :file:`config/_shared/rhodecode.ini` file.
53
53
54 .. code-block:: ini
54 .. code-block:: ini
55
55
56 ## redis sessions
56 ## redis sessions
57 beaker.session.type = ext:redis
57 beaker.session.type = ext:redis
58 beaker.session.url = localhost:6379
58 beaker.session.url = localhost:6379
59
59
60
60
61 and make sure you comment out the file based sessions.
61 and make sure you comment out the file based sessions.
62
62
63 .. code-block:: ini
63 .. code-block:: ini
64
64
65 ## types are file, ext:memcached, ext:database, and memory (default).
65 ## types are file, ext:memcached, ext:database, and memory (default).
66 #beaker.session.type = file
66 #beaker.session.type = file
67 #beaker.session.data_dir = %(here)s/data/sessions/data No newline at end of file
67 #beaker.session.data_dir = %(here)s/data/sessions/data
@@ -1,379 +1,379 b''
1 .. _vcs-server:
1 .. _vcs-server:
2
2
3 VCS Server Management
3 VCS Server Management
4 ---------------------
4 ---------------------
5
5
6 The VCS Server handles |RCE| backend functionality. You need to configure
6 The VCS Server handles |RCE| backend functionality. You need to configure
7 a VCS Server to run with a |RCE| instance. If you do not, you will be missing
7 a VCS Server to run with a |RCE| instance. If you do not, you will be missing
8 the connection between |RCE| and its |repos|. This will cause error messages
8 the connection between |RCE| and its |repos|. This will cause error messages
9 on the web interface. You can run your setup in the following configurations,
9 on the web interface. You can run your setup in the following configurations,
10 currently the best performance is one of following:
10 currently the best performance is one of following:
11
11
12 * One VCS Server per |RCE| instance.
12 * One VCS Server per |RCE| instance.
13 * One VCS Server handling multiple instances.
13 * One VCS Server handling multiple instances.
14
14
15 .. important::
15 .. important::
16
16
17 If your server locale settings are not correctly configured,
17 If your server locale settings are not correctly configured,
18 |RCE| and the VCS Server can run into issues. See this `Ask Ubuntu`_ post
18 |RCE| and the VCS Server can run into issues. See this `Ask Ubuntu`_ post
19 which explains the problem and gives a solution.
19 which explains the problem and gives a solution.
20
20
21 For more information, see the following sections:
21 For more information, see the following sections:
22
22
23 * :ref:`install-vcs`
23 * :ref:`install-vcs`
24 * :ref:`config-vcs`
24 * :ref:`config-vcs`
25 * :ref:`vcs-server-options`
25 * :ref:`vcs-server-options`
26 * :ref:`vcs-server-versions`
26 * :ref:`vcs-server-versions`
27 * :ref:`vcs-server-maintain`
27 * :ref:`vcs-server-maintain`
28 * :ref:`vcs-server-config-file`
28 * :ref:`vcs-server-config-file`
29 * :ref:`svn-http`
29 * :ref:`svn-http`
30
30
31 .. _install-vcs:
31 .. _install-vcs:
32
32
33 VCS Server Installation
33 VCS Server Installation
34 ^^^^^^^^^^^^^^^^^^^^^^^
34 ^^^^^^^^^^^^^^^^^^^^^^^
35
35
36 To install a VCS Server, see
36 To install a VCS Server, see
37 :ref:`Installing a VCS server <control:install-vcsserver>`.
37 :ref:`Installing a VCS server <control:install-vcsserver>`.
38
38
39 .. _config-vcs:
39 .. _config-vcs:
40
40
41 Hooking |RCE| to its VCS Server
41 Hooking |RCE| to its VCS Server
42 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
42 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
43
43
44 To configure a |RCE| instance to use a VCS server, see
44 To configure a |RCE| instance to use a VCS server, see
45 :ref:`Configuring the VCS Server connection <control:manually-vcsserver-ini>`.
45 :ref:`Configuring the VCS Server connection <control:manually-vcsserver-ini>`.
46
46
47 .. _vcs-server-options:
47 .. _vcs-server-options:
48
48
49 |RCE| VCS Server Options
49 |RCE| VCS Server Options
50 ^^^^^^^^^^^^^^^^^^^^^^^^
50 ^^^^^^^^^^^^^^^^^^^^^^^^
51
51
52 The following list shows the available options on the |RCE| side of the
52 The following list shows the available options on the |RCE| side of the
53 connection to the VCS Server. The settings are configured per
53 connection to the VCS Server. The settings are configured per
54 instance in the
54 instance in the
55 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file.
55 :file:`config/_shared/rhodecode.ini` file.
56
56
57 .. rst-class:: dl-horizontal
57 .. rst-class:: dl-horizontal
58
58
59 \vcs.backends <available-vcs-systems>
59 \vcs.backends <available-vcs-systems>
60 Set a comma-separated list of the |repo| options available from the
60 Set a comma-separated list of the |repo| options available from the
61 web interface. The default is ``hg, git, svn``,
61 web interface. The default is ``hg, git, svn``,
62 which is all |repo| types available. The order of backends is also the
62 which is all |repo| types available. The order of backends is also the
63 order backend will try to detect requests type.
63 order backend will try to detect requests type.
64
64
65 \vcs.connection_timeout <seconds>
65 \vcs.connection_timeout <seconds>
66 Set the length of time in seconds that the VCS Server waits for
66 Set the length of time in seconds that the VCS Server waits for
67 requests to process. After the timeout expires,
67 requests to process. After the timeout expires,
68 the request is closed. The default is ``3600``. Set to a higher
68 the request is closed. The default is ``3600``. Set to a higher
69 number if you experience network latency, or timeout issues with very
69 number if you experience network latency, or timeout issues with very
70 large push/pull requests.
70 large push/pull requests.
71
71
72 \vcs.server.enable <boolean>
72 \vcs.server.enable <boolean>
73 Enable or disable the VCS Server. The available options are ``true`` or
73 Enable or disable the VCS Server. The available options are ``true`` or
74 ``false``. The default is ``true``.
74 ``false``. The default is ``true``.
75
75
76 \vcs.server <host:port>
76 \vcs.server <host:port>
77 Set the host, either hostname or IP Address, and port of the VCS server
77 Set the host, either hostname or IP Address, and port of the VCS server
78 you wish to run with your |RCE| instance.
78 you wish to run with your |RCE| instance.
79
79
80 .. code-block:: ini
80 .. code-block:: ini
81
81
82 ##################
82 ##################
83 ### VCS CONFIG ###
83 ### VCS CONFIG ###
84 ##################
84 ##################
85 # set this line to match your VCS Server
85 # set this line to match your VCS Server
86 vcs.server = 127.0.0.1:10004
86 vcs.server = 127.0.0.1:10004
87 # Set to False to disable the VCS Server
87 # Set to False to disable the VCS Server
88 vcs.server.enable = True
88 vcs.server.enable = True
89 vcs.backends = hg, git, svn
89 vcs.backends = hg, git, svn
90 vcs.connection_timeout = 3600
90 vcs.connection_timeout = 3600
91
91
92
92
93 .. _vcs-server-versions:
93 .. _vcs-server-versions:
94
94
95 VCS Server Versions
95 VCS Server Versions
96 ^^^^^^^^^^^^^^^^^^^
96 ^^^^^^^^^^^^^^^^^^^
97
97
98 An updated version of the VCS Server is released with each |RCE| version. Use
98 An updated version of the VCS Server is released with each |RCE| version. Use
99 the VCS Server number that matches with the |RCE| version to pair the
99 the VCS Server number that matches with the |RCE| version to pair the
100 appropriate ones together. For |RCE| versions pre 3.3.0,
100 appropriate ones together. For |RCE| versions pre 3.3.0,
101 VCS Server 1.X.Y works with |RCE| 3.X.Y, for example:
101 VCS Server 1.X.Y works with |RCE| 3.X.Y, for example:
102
102
103 * VCS Server 1.0.0 works with |RCE| 3.0.0
103 * VCS Server 1.0.0 works with |RCE| 3.0.0
104 * VCS Server 1.2.2 works with |RCE| 3.2.2
104 * VCS Server 1.2.2 works with |RCE| 3.2.2
105
105
106 For |RCE| versions post 3.3.0, the VCS Server and |RCE| version numbers
106 For |RCE| versions post 3.3.0, the VCS Server and |RCE| version numbers
107 match, for example:
107 match, for example:
108
108
109 * VCS Server |release| works with |RCE| |release|
109 * VCS Server |release| works with |RCE| |release|
110
110
111 .. _vcs-server-maintain:
111 .. _vcs-server-maintain:
112
112
113 VCS Server Cache Optimization
113 VCS Server Cache Optimization
114 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
114 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
115
115
116 To optimize the VCS server to manage the cache and memory usage efficiently, it's recommended to
116 To optimize the VCS server to manage the cache and memory usage efficiently, it's recommended to
117 configure the Redis backend for VCSServer caches.
117 configure the Redis backend for VCSServer caches.
118 Once configured, restart the VCS Server.
118 Once configured, restart the VCS Server.
119
119
120 Make sure Redis is installed and running.
120 Make sure Redis is installed and running.
121 Open :file:`/home/{user}/.rccontrol/{vcsserver-id}/vcsserver.ini`
121 Open :file:`/home/{user}/.rccontrol/{vcsserver-id}/vcsserver.ini`
122 file and ensure the below settings for `repo_object` type cache are set:
122 file and ensure the below settings for `repo_object` type cache are set:
123
123
124 .. code-block:: ini
124 .. code-block:: ini
125
125
126 ; ensure the default file based cache is *commented out*
126 ; ensure the default file based cache is *commented out*
127 ##rc_cache.repo_object.backend = dogpile.cache.rc.file_namespace
127 ##rc_cache.repo_object.backend = dogpile.cache.rc.file_namespace
128 ##rc_cache.repo_object.expiration_time = 2592000
128 ##rc_cache.repo_object.expiration_time = 2592000
129
129
130 ; `repo_object` cache settings for vcs methods for repositories
130 ; `repo_object` cache settings for vcs methods for repositories
131 rc_cache.repo_object.backend = dogpile.cache.rc.redis_msgpack
131 rc_cache.repo_object.backend = dogpile.cache.rc.redis_msgpack
132
132
133 ; cache auto-expires after N seconds
133 ; cache auto-expires after N seconds
134 ; Examples: 86400 (1Day), 604800 (7Days), 1209600 (14Days), 2592000 (30days), 7776000 (90Days)
134 ; Examples: 86400 (1Day), 604800 (7Days), 1209600 (14Days), 2592000 (30days), 7776000 (90Days)
135 rc_cache.repo_object.expiration_time = 2592000
135 rc_cache.repo_object.expiration_time = 2592000
136
136
137 ; redis_expiration_time needs to be greater then expiration_time
137 ; redis_expiration_time needs to be greater then expiration_time
138 rc_cache.repo_object.arguments.redis_expiration_time = 3592000
138 rc_cache.repo_object.arguments.redis_expiration_time = 3592000
139
139
140 rc_cache.repo_object.arguments.host = localhost
140 rc_cache.repo_object.arguments.host = localhost
141 rc_cache.repo_object.arguments.port = 6379
141 rc_cache.repo_object.arguments.port = 6379
142 rc_cache.repo_object.arguments.db = 5
142 rc_cache.repo_object.arguments.db = 5
143 rc_cache.repo_object.arguments.socket_timeout = 30
143 rc_cache.repo_object.arguments.socket_timeout = 30
144 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
144 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
145 rc_cache.repo_object.arguments.distributed_lock = true
145 rc_cache.repo_object.arguments.distributed_lock = true
146
146
147
147
148 To clear the cache completely, you can restart the VCS Server.
148 To clear the cache completely, you can restart the VCS Server.
149
149
150 .. important::
150 .. important::
151
151
152 While the VCS Server handles a restart gracefully on the web interface,
152 While the VCS Server handles a restart gracefully on the web interface,
153 it will drop connections during push/pull requests. So it is recommended
153 it will drop connections during push/pull requests. So it is recommended
154 you only perform this when there is very little traffic on the instance.
154 you only perform this when there is very little traffic on the instance.
155
155
156 Use the following example to restart your VCS Server,
156 Use the following example to restart your VCS Server,
157 for full details see the :ref:`RhodeCode Control CLI <control:rcc-cli>`.
157 for full details see the :ref:`RhodeCode Control CLI <control:rcc-cli>`.
158
158
159 .. code-block:: bash
159 .. code-block:: bash
160
160
161 $ rccontrol status
161 $ rccontrol status
162
162
163 .. code-block:: vim
163 .. code-block:: vim
164
164
165 - NAME: vcsserver-1
165 - NAME: vcsserver-1
166 - STATUS: RUNNING
166 - STATUS: RUNNING
167 logs:/home/ubuntu/.rccontrol/vcsserver-1/vcsserver.log
167 logs:/home/ubuntu/.rccontrol/vcsserver-1/vcsserver.log
168 - VERSION: 4.7.2 VCSServer
168 - VERSION: 4.7.2 VCSServer
169 - URL: http://127.0.0.1:10008
169 - URL: http://127.0.0.1:10008
170 - CONFIG: /home/ubuntu/.rccontrol/vcsserver-1/vcsserver.ini
170 - CONFIG: /home/ubuntu/.rccontrol/vcsserver-1/vcsserver.ini
171
171
172 $ rccontrol restart vcsserver-1
172 $ rccontrol restart vcsserver-1
173 Instance "vcsserver-1" successfully stopped.
173 Instance "vcsserver-1" successfully stopped.
174 Instance "vcsserver-1" successfully started.
174 Instance "vcsserver-1" successfully started.
175
175
176 .. _vcs-server-config-file:
176 .. _vcs-server-config-file:
177
177
178 VCS Server Configuration
178 VCS Server Configuration
179 ^^^^^^^^^^^^^^^^^^^^^^^^
179 ^^^^^^^^^^^^^^^^^^^^^^^^
180
180
181 You can configure settings for multiple VCS Servers on your
181 You can configure settings for multiple VCS Servers on your
182 system using their individual configuration files. Use the following
182 system using their individual configuration files. Use the following
183 properties inside the configuration file to set up your system. The default
183 properties inside the configuration file to set up your system. The default
184 location is :file:`home/{user}/.rccontrol/{vcsserver-id}/vcsserver.ini`.
184 location is :file:`home/{user}/.rccontrol/{vcsserver-id}/vcsserver.ini`.
185 For a more detailed explanation of the logger levers, see :ref:`debug-mode`.
185 For a more detailed explanation of the logger levers, see :ref:`debug-mode`.
186
186
187 .. rst-class:: dl-horizontal
187 .. rst-class:: dl-horizontal
188
188
189 \host <ip-address>
189 \host <ip-address>
190 Set the host on which the VCS Server will run. VCSServer is not
190 Set the host on which the VCS Server will run. VCSServer is not
191 protected by any authentication, so we *highly* recommend running it
191 protected by any authentication, so we *highly* recommend running it
192 under localhost ip that is `127.0.0.1`
192 under localhost ip that is `127.0.0.1`
193
193
194 \port <int>
194 \port <int>
195 Set the port number on which the VCS Server will be available.
195 Set the port number on which the VCS Server will be available.
196
196
197
197
198 .. note::
198 .. note::
199
199
200 After making changes, you need to restart your VCS Server to pick them up.
200 After making changes, you need to restart your VCS Server to pick them up.
201
201
202 .. code-block:: ini
202 .. code-block:: ini
203
203
204 ; #################################
204 ; #################################
205 ; RHODECODE VCSSERVER CONFIGURATION
205 ; RHODECODE VCSSERVER CONFIGURATION
206 ; #################################
206 ; #################################
207
207
208 [server:main]
208 [server:main]
209 ; COMMON HOST/IP CONFIG
209 ; COMMON HOST/IP CONFIG
210 host = 127.0.0.1
210 host = 127.0.0.1
211 port = 10002
211 port = 10002
212
212
213 ; ###########################
213 ; ###########################
214 ; GUNICORN APPLICATION SERVER
214 ; GUNICORN APPLICATION SERVER
215 ; ###########################
215 ; ###########################
216
216
217 ; run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
217 ; run with gunicorn --log-config rhodecode.ini --paste rhodecode.ini
218
218
219 ; Module to use, this setting shouldn't be changed
219 ; Module to use, this setting shouldn't be changed
220 use = egg:gunicorn#main
220 use = egg:gunicorn#main
221
221
222 ; Sets the number of process workers. More workers means more concurrent connections
222 ; Sets the number of process workers. More workers means more concurrent connections
223 ; RhodeCode can handle at the same time. Each additional worker also it increases
223 ; RhodeCode can handle at the same time. Each additional worker also it increases
224 ; memory usage as each has it's own set of caches.
224 ; memory usage as each has it's own set of caches.
225 ; Recommended value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers, but no more
225 ; Recommended value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers, but no more
226 ; than 8-10 unless for really big deployments .e.g 700-1000 users.
226 ; than 8-10 unless for really big deployments .e.g 700-1000 users.
227 ; `instance_id = *` must be set in the [app:main] section below (which is the default)
227 ; `instance_id = *` must be set in the [app:main] section below (which is the default)
228 ; when using more than 1 worker.
228 ; when using more than 1 worker.
229 workers = 6
229 workers = 6
230
230
231 ; Gunicorn access log level
231 ; Gunicorn access log level
232 loglevel = info
232 loglevel = info
233
233
234 ; Process name visible in process list
234 ; Process name visible in process list
235 proc_name = rhodecode_vcsserver
235 proc_name = rhodecode_vcsserver
236
236
237 ; Type of worker class, one of sync, gevent
237 ; Type of worker class, one of sync, gevent
238 ; currently `sync` is the only option allowed.
238 ; currently `sync` is the only option allowed.
239 worker_class = sync
239 worker_class = sync
240
240
241 ; The maximum number of simultaneous clients. Valid only for gevent
241 ; The maximum number of simultaneous clients. Valid only for gevent
242 worker_connections = 10
242 worker_connections = 10
243
243
244 ; Max number of requests that worker will handle before being gracefully restarted.
244 ; Max number of requests that worker will handle before being gracefully restarted.
245 ; Prevents memory leaks, jitter adds variability so not all workers are restarted at once.
245 ; Prevents memory leaks, jitter adds variability so not all workers are restarted at once.
246 max_requests = 1000
246 max_requests = 1000
247 max_requests_jitter = 30
247 max_requests_jitter = 30
248
248
249 ; Amount of time a worker can spend with handling a request before it
249 ; Amount of time a worker can spend with handling a request before it
250 ; gets killed and restarted. By default set to 21600 (6hrs)
250 ; gets killed and restarted. By default set to 21600 (6hrs)
251 ; Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
251 ; Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
252 timeout = 21600
252 timeout = 21600
253
253
254 ; The maximum size of HTTP request line in bytes.
254 ; The maximum size of HTTP request line in bytes.
255 ; 0 for unlimited
255 ; 0 for unlimited
256 limit_request_line = 0
256 limit_request_line = 0
257
257
258 ; Limit the number of HTTP headers fields in a request.
258 ; Limit the number of HTTP headers fields in a request.
259 ; By default this value is 100 and can't be larger than 32768.
259 ; By default this value is 100 and can't be larger than 32768.
260 limit_request_fields = 32768
260 limit_request_fields = 32768
261
261
262 ; Limit the allowed size of an HTTP request header field.
262 ; Limit the allowed size of an HTTP request header field.
263 ; Value is a positive number or 0.
263 ; Value is a positive number or 0.
264 ; Setting it to 0 will allow unlimited header field sizes.
264 ; Setting it to 0 will allow unlimited header field sizes.
265 limit_request_field_size = 0
265 limit_request_field_size = 0
266
266
267 ; Timeout for graceful workers restart.
267 ; Timeout for graceful workers restart.
268 ; After receiving a restart signal, workers have this much time to finish
268 ; After receiving a restart signal, workers have this much time to finish
269 ; serving requests. Workers still alive after the timeout (starting from the
269 ; serving requests. Workers still alive after the timeout (starting from the
270 ; receipt of the restart signal) are force killed.
270 ; receipt of the restart signal) are force killed.
271 ; Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
271 ; Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
272 graceful_timeout = 3600
272 graceful_timeout = 3600
273
273
274 # The number of seconds to wait for requests on a Keep-Alive connection.
274 # The number of seconds to wait for requests on a Keep-Alive connection.
275 # Generally set in the 1-5 seconds range.
275 # Generally set in the 1-5 seconds range.
276 keepalive = 2
276 keepalive = 2
277
277
278 ; Maximum memory usage that each worker can use before it will receive a
278 ; Maximum memory usage that each worker can use before it will receive a
279 ; graceful restart signal 0 = memory monitoring is disabled
279 ; graceful restart signal 0 = memory monitoring is disabled
280 ; Examples: 268435456 (256MB), 536870912 (512MB)
280 ; Examples: 268435456 (256MB), 536870912 (512MB)
281 ; 1073741824 (1GB), 2147483648 (2GB), 4294967296 (4GB)
281 ; 1073741824 (1GB), 2147483648 (2GB), 4294967296 (4GB)
282 memory_max_usage = 1073741824
282 memory_max_usage = 1073741824
283
283
284 ; How often in seconds to check for memory usage for each gunicorn worker
284 ; How often in seconds to check for memory usage for each gunicorn worker
285 memory_usage_check_interval = 60
285 memory_usage_check_interval = 60
286
286
287 ; Threshold value for which we don't recycle worker if GarbageCollection
287 ; Threshold value for which we don't recycle worker if GarbageCollection
288 ; frees up enough resources. Before each restart we try to run GC on worker
288 ; frees up enough resources. Before each restart we try to run GC on worker
289 ; in case we get enough free memory after that, restart will not happen.
289 ; in case we get enough free memory after that, restart will not happen.
290 memory_usage_recovery_threshold = 0.8
290 memory_usage_recovery_threshold = 0.8
291
291
292
292
293 [app:main]
293 [app:main]
294 use = egg:rhodecode-vcsserver
294 use = egg:rhodecode-vcsserver
295
295
296 pyramid.default_locale_name = en
296 pyramid.default_locale_name = en
297 pyramid.includes =
297 pyramid.includes =
298
298
299 ; default locale used by VCS systems
299 ; default locale used by VCS systems
300 locale = en_US.UTF-8
300 locale = en_US.UTF-8
301
301
302 ; #############
302 ; #############
303 ; DOGPILE CACHE
303 ; DOGPILE CACHE
304 ; #############
304 ; #############
305
305
306 ; Default cache dir for caches. Putting this into a ramdisk can boost performance.
306 ; Default cache dir for caches. Putting this into a ramdisk can boost performance.
307 ; eg. /tmpfs/data_ramdisk, however this directory might require large amount of space
307 ; eg. /tmpfs/data_ramdisk, however this directory might require large amount of space
308 cache_dir = %(here)s/data
308 cache_dir = %(here)s/data
309
309
310 ; **********************************************************
310 ; **********************************************************
311 ; `repo_object` cache with redis backend
311 ; `repo_object` cache with redis backend
312 ; recommended for larger instance, or for better performance
312 ; recommended for larger instance, or for better performance
313 ; **********************************************************
313 ; **********************************************************
314
314
315 ; `repo_object` cache settings for vcs methods for repositories
315 ; `repo_object` cache settings for vcs methods for repositories
316 rc_cache.repo_object.backend = dogpile.cache.rc.redis_msgpack
316 rc_cache.repo_object.backend = dogpile.cache.rc.redis_msgpack
317
317
318 ; cache auto-expires after N seconds
318 ; cache auto-expires after N seconds
319 ; Examples: 86400 (1Day), 604800 (7Days), 1209600 (14Days), 2592000 (30days), 7776000 (90Days)
319 ; Examples: 86400 (1Day), 604800 (7Days), 1209600 (14Days), 2592000 (30days), 7776000 (90Days)
320 rc_cache.repo_object.expiration_time = 2592000
320 rc_cache.repo_object.expiration_time = 2592000
321
321
322 ; redis_expiration_time needs to be greater then expiration_time
322 ; redis_expiration_time needs to be greater then expiration_time
323 rc_cache.repo_object.arguments.redis_expiration_time = 3592000
323 rc_cache.repo_object.arguments.redis_expiration_time = 3592000
324
324
325 rc_cache.repo_object.arguments.host = localhost
325 rc_cache.repo_object.arguments.host = localhost
326 rc_cache.repo_object.arguments.port = 6379
326 rc_cache.repo_object.arguments.port = 6379
327 rc_cache.repo_object.arguments.db = 5
327 rc_cache.repo_object.arguments.db = 5
328 rc_cache.repo_object.arguments.socket_timeout = 30
328 rc_cache.repo_object.arguments.socket_timeout = 30
329 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
329 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
330 rc_cache.repo_object.arguments.distributed_lock = true
330 rc_cache.repo_object.arguments.distributed_lock = true
331
331
332 ; #####################
332 ; #####################
333 ; LOGGING CONFIGURATION
333 ; LOGGING CONFIGURATION
334 ; #####################
334 ; #####################
335 [loggers]
335 [loggers]
336 keys = root, vcsserver
336 keys = root, vcsserver
337
337
338 [handlers]
338 [handlers]
339 keys = console
339 keys = console
340
340
341 [formatters]
341 [formatters]
342 keys = generic
342 keys = generic
343
343
344 ; #######
344 ; #######
345 ; LOGGERS
345 ; LOGGERS
346 ; #######
346 ; #######
347 [logger_root]
347 [logger_root]
348 level = NOTSET
348 level = NOTSET
349 handlers = console
349 handlers = console
350
350
351 [logger_vcsserver]
351 [logger_vcsserver]
352 level = DEBUG
352 level = DEBUG
353 handlers =
353 handlers =
354 qualname = vcsserver
354 qualname = vcsserver
355 propagate = 1
355 propagate = 1
356
356
357
357
358 ; ########
358 ; ########
359 ; HANDLERS
359 ; HANDLERS
360 ; ########
360 ; ########
361
361
362 [handler_console]
362 [handler_console]
363 class = StreamHandler
363 class = StreamHandler
364 args = (sys.stderr, )
364 args = (sys.stderr, )
365 level = INFO
365 level = INFO
366 formatter = generic
366 formatter = generic
367
367
368 ; ##########
368 ; ##########
369 ; FORMATTERS
369 ; FORMATTERS
370 ; ##########
370 ; ##########
371
371
372 [formatter_generic]
372 [formatter_generic]
373 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
373 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
374 datefmt = %Y-%m-%d %H:%M:%S
374 datefmt = %Y-%m-%d %H:%M:%S
375
375
376
376
377 .. _Subversion Red Book: http://svnbook.red-bean.com/en/1.7/svn-book.html#svn.ref.svn
377 .. _Subversion Red Book: http://svnbook.red-bean.com/en/1.7/svn-book.html#svn.ref.svn
378
378
379 .. _Ask Ubuntu: http://askubuntu.com/questions/162391/how-do-i-fix-my-locale-issue
379 .. _Ask Ubuntu: http://askubuntu.com/questions/162391/how-do-i-fix-my-locale-issue
@@ -1,209 +1,209 b''
1 .. _api:
1 .. _api:
2
2
3 API Documentation
3 API Documentation
4 =================
4 =================
5
5
6 The |RCE| API uses a single scheme for calling all API methods. The API is
6 The |RCE| API uses a single scheme for calling all API methods. The API is
7 implemented with JSON protocol in both directions. To send API requests to
7 implemented with JSON protocol in both directions. To send API requests to
8 your instance of |RCE|, use the following URL format
8 your instance of |RCE|, use the following URL format
9 ``<your_server>/_admin``
9 ``<your_server>/_admin``
10
10
11 .. note::
11 .. note::
12
12
13 To use the API, you should configure the :file:`~/.rhoderc` file with
13 To use the API, you should configure the :file:`~/.rhoderc` file with
14 access details per instance. For more information, see
14 access details per instance. For more information, see
15 :ref:`config-rhoderc`.
15 :ref:`config-rhoderc`.
16
16
17
17
18 API ACCESS FOR WEB VIEWS
18 API ACCESS FOR WEB VIEWS
19 ------------------------
19 ------------------------
20
20
21 API access can also be turned on for each web view in |RCE| that is
21 API access can also be turned on for each web view in |RCE| that is
22 decorated with a `@LoginRequired` decorator. To enable API access, change
22 decorated with a `@LoginRequired` decorator. To enable API access, change
23 the standard login decorator to `@LoginRequired(api_access=True)`.
23 the standard login decorator to `@LoginRequired(api_access=True)`.
24
24
25 From |RCE| version 1.7.0 you can configure a white list
25 From |RCE| version 1.7.0 you can configure a white list
26 of views that have API access enabled by default. To enable these,
26 of views that have API access enabled by default. To enable these,
27 edit the |RCE| configuration ``.ini`` file. The default location is:
27 edit the |RCE| configuration ``.ini`` file. The default location is:
28
28
29 * |RCE| Pre-2.2.7 :file:`root/rhodecode/data/production.ini`
29 * |RCE| Pre-2.2.7 :file:`root/rhodecode/data/production.ini`
30 * |RCE| 3.0 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
30 * |RCE| 3.0 :file:`config/_shared/rhodecode.ini`
31
31
32 To configure the white list, edit this section of the file. In this
32 To configure the white list, edit this section of the file. In this
33 configuration example, API access is granted to the patch/diff raw file and
33 configuration example, API access is granted to the patch/diff raw file and
34 archive.
34 archive.
35
35
36 .. code-block:: ini
36 .. code-block:: ini
37
37
38 ## List of controllers (using glob syntax) that AUTH TOKENS could be used for access.
38 ## List of controllers (using glob syntax) that AUTH TOKENS could be used for access.
39 ## Adding ?auth_token = <token> to the url authenticates this request as if it
39 ## Adding ?auth_token = <token> to the url authenticates this request as if it
40 ## came from the the logged in user who own this authentication token.
40 ## came from the the logged in user who own this authentication token.
41 ##
41 ##
42 ## Syntax is <ControllerClass>:<function_pattern>.
42 ## Syntax is <ControllerClass>:<function_pattern>.
43 ## The list should be "," separated and on a single line.
43 ## The list should be "," separated and on a single line.
44 ##
44 ##
45 api_access_controllers_whitelist = RepoCommitsView:repo_commit_raw,RepoCommitsView:repo_commit_patch,RepoCommitsView:repo_commit_download
45 api_access_controllers_whitelist = RepoCommitsView:repo_commit_raw,RepoCommitsView:repo_commit_patch,RepoCommitsView:repo_commit_download
46
46
47 After this change, a |RCE| view can be accessed without login by adding a
47 After this change, a |RCE| view can be accessed without login by adding a
48 GET parameter ``?auth_token=<auth_token>`` to a url. For example to
48 GET parameter ``?auth_token=<auth_token>`` to a url. For example to
49 access the raw diff.
49 access the raw diff.
50
50
51 .. code-block:: html
51 .. code-block:: html
52
52
53 http://<server>/<repo>/changeset-diff/<sha>?auth_token=<auth_token>
53 http://<server>/<repo>/changeset-diff/<sha>?auth_token=<auth_token>
54
54
55 By default this is only enabled on RSS/ATOM feed views. Exposing raw diffs is a
55 By default this is only enabled on RSS/ATOM feed views. Exposing raw diffs is a
56 good way to integrate with 3rd party services like code review, or build farms
56 good way to integrate with 3rd party services like code review, or build farms
57 that could download archives.
57 that could download archives.
58
58
59 API ACCESS
59 API ACCESS
60 ----------
60 ----------
61
61
62 All clients are required to send JSON-RPC spec JSON data.
62 All clients are required to send JSON-RPC spec JSON data.
63
63
64 .. code-block:: bash
64 .. code-block:: bash
65
65
66 {
66 {
67 "id:"<id>",
67 "id:"<id>",
68 "auth_token":"<auth_token>",
68 "auth_token":"<auth_token>",
69 "method":"<method_name>",
69 "method":"<method_name>",
70 "args":{"<arg_key>":"<arg_val>"}
70 "args":{"<arg_key>":"<arg_val>"}
71 }
71 }
72
72
73 Example call for auto pulling from remote repositories using curl:
73 Example call for auto pulling from remote repositories using curl:
74
74
75 .. code-block:: bash
75 .. code-block:: bash
76
76
77 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"id":1,
77 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"id":1,
78 "auth_token":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull", "args":{"repoid":"CPython"}}'
78 "auth_token":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull", "args":{"repoid":"CPython"}}'
79
79
80 Provide those parameters:
80 Provide those parameters:
81 - **id** A value of any type, which is used to match the response with the
81 - **id** A value of any type, which is used to match the response with the
82 request that it is replying to.
82 request that it is replying to.
83 - **auth_token** for access and permission validation.
83 - **auth_token** for access and permission validation.
84 - **method** is name of method to call
84 - **method** is name of method to call
85 - **args** is an ``key:value`` list of arguments to pass to method
85 - **args** is an ``key:value`` list of arguments to pass to method
86
86
87 .. note::
87 .. note::
88
88
89 To get your |authtoken|, from the |RCE| interface,
89 To get your |authtoken|, from the |RCE| interface,
90 go to:
90 go to:
91 :menuselection:`username --> My account --> Auth tokens`
91 :menuselection:`username --> My account --> Auth tokens`
92
92
93 For security reasons you should always create a dedicated |authtoken| for
93 For security reasons you should always create a dedicated |authtoken| for
94 API use only.
94 API use only.
95
95
96
96
97 The |RCE| API will always return a JSON-RPC response:
97 The |RCE| API will always return a JSON-RPC response:
98
98
99 .. code-block:: bash
99 .. code-block:: bash
100
100
101 {
101 {
102 "id": <id>, # matching id sent by request
102 "id": <id>, # matching id sent by request
103 "result": "<result>"|null, # JSON formatted result, null if any errors
103 "result": "<result>"|null, # JSON formatted result, null if any errors
104 "error": "null"|<error_message> # JSON formatted error (if any)
104 "error": "null"|<error_message> # JSON formatted error (if any)
105 }
105 }
106
106
107 All responses from API will be with `HTTP/1.0 200 OK` status code.
107 All responses from API will be with `HTTP/1.0 200 OK` status code.
108 If there is an error when calling the API, the *error* key will contain a
108 If there is an error when calling the API, the *error* key will contain a
109 failure description and the *result* will be `null`.
109 failure description and the *result* will be `null`.
110
110
111 API CLIENT
111 API CLIENT
112 ----------
112 ----------
113
113
114 To install the |RCE| API, see :ref:`install-tools`. To configure the API per
114 To install the |RCE| API, see :ref:`install-tools`. To configure the API per
115 instance, see the :ref:`rc-tools` section as you need to configure a
115 instance, see the :ref:`rc-tools` section as you need to configure a
116 :file:`~/.rhoderc` file with your |authtokens|.
116 :file:`~/.rhoderc` file with your |authtokens|.
117
117
118 Once you have set up your instance API access, use the following examples to
118 Once you have set up your instance API access, use the following examples to
119 get started.
119 get started.
120
120
121 .. code-block:: bash
121 .. code-block:: bash
122
122
123 # Getting the 'rhodecode' repository
123 # Getting the 'rhodecode' repository
124 # from a RhodeCode Enterprise instance
124 # from a RhodeCode Enterprise instance
125 rhodecode-api --instance-name=enterprise-1 get_repo repoid:rhodecode
125 rhodecode-api --instance-name=enterprise-1 get_repo repoid:rhodecode
126
126
127 Calling method get_repo => http://127.0.0.1:5000
127 Calling method get_repo => http://127.0.0.1:5000
128 Server response
128 Server response
129 {
129 {
130 <json data>
130 <json data>
131 }
131 }
132
132
133 # Creating a new mercurial repository called 'brand-new'
133 # Creating a new mercurial repository called 'brand-new'
134 # with a description 'Repo-description'
134 # with a description 'Repo-description'
135 rhodecode-api --instance-name=enterprise-1 create_repo repo_name:brand-new repo_type:hg description:Repo-description
135 rhodecode-api --instance-name=enterprise-1 create_repo repo_name:brand-new repo_type:hg description:Repo-description
136 {
136 {
137 "error": null,
137 "error": null,
138 "id": 1110,
138 "id": 1110,
139 "result": {
139 "result": {
140 "msg": "Created new repository `brand-new`",
140 "msg": "Created new repository `brand-new`",
141 "success": true,
141 "success": true,
142 "task": null
142 "task": null
143 }
143 }
144 }
144 }
145
145
146 A broken example, what not to do.
146 A broken example, what not to do.
147
147
148 .. code-block:: bash
148 .. code-block:: bash
149
149
150 # A call missing the required arguments
150 # A call missing the required arguments
151 # and not specifying the instance
151 # and not specifying the instance
152 rhodecode-api get_repo
152 rhodecode-api get_repo
153
153
154 Calling method get_repo => http://127.0.0.1:5000
154 Calling method get_repo => http://127.0.0.1:5000
155 Server response
155 Server response
156 "Missing non optional `repoid` arg in JSON DATA"
156 "Missing non optional `repoid` arg in JSON DATA"
157
157
158 You can specify pure JSON using the ``--format`` parameter.
158 You can specify pure JSON using the ``--format`` parameter.
159
159
160 .. code-block:: bash
160 .. code-block:: bash
161
161
162 rhodecode-api --format=json get_repo repoid:rhodecode
162 rhodecode-api --format=json get_repo repoid:rhodecode
163
163
164 In such case only output that this function shows is pure JSON, we can use that
164 In such case only output that this function shows is pure JSON, we can use that
165 and pipe output to some json formatter.
165 and pipe output to some json formatter.
166
166
167 If output is in pure JSON format, you can pipe output to a JSON formatter.
167 If output is in pure JSON format, you can pipe output to a JSON formatter.
168
168
169 .. code-block:: bash
169 .. code-block:: bash
170
170
171 rhodecode-api --instance-name=enterprise-1 --format=json get_repo repoid:rhodecode | python -m json.tool
171 rhodecode-api --instance-name=enterprise-1 --format=json get_repo repoid:rhodecode | python -m json.tool
172
172
173 API METHODS
173 API METHODS
174 -----------
174 -----------
175
175
176 Each method by default required following arguments.
176 Each method by default required following arguments.
177
177
178 .. code-block:: bash
178 .. code-block:: bash
179
179
180 id : "<id_for_response>"
180 id : "<id_for_response>"
181 auth_token : "<auth_token>"
181 auth_token : "<auth_token>"
182 method : "<method name>"
182 method : "<method name>"
183 args : {}
183 args : {}
184
184
185 Use each **param** from docs and put it in args, Optional parameters
185 Use each **param** from docs and put it in args, Optional parameters
186 are not required in args.
186 are not required in args.
187
187
188 .. code-block:: bash
188 .. code-block:: bash
189
189
190 args: {"repoid": "rhodecode"}
190 args: {"repoid": "rhodecode"}
191
191
192 .. Note: From this point on things are generated by the script in
192 .. Note: From this point on things are generated by the script in
193 `scripts/fabfile.py`. To change things below, update the docstrings in the
193 `scripts/fabfile.py`. To change things below, update the docstrings in the
194 ApiController.
194 ApiController.
195
195
196 .. --- API DEFS MARKER ---
196 .. --- API DEFS MARKER ---
197 .. toctree::
197 .. toctree::
198
198
199 methods/repo-methods
199 methods/repo-methods
200 methods/store-methods
200 methods/store-methods
201 methods/license-methods
201 methods/license-methods
202 methods/deprecated-methods
202 methods/deprecated-methods
203 methods/gist-methods
203 methods/gist-methods
204 methods/pull-request-methods
204 methods/pull-request-methods
205 methods/repo-group-methods
205 methods/repo-group-methods
206 methods/search-methods
206 methods/search-methods
207 methods/server-methods
207 methods/server-methods
208 methods/user-methods
208 methods/user-methods
209 methods/user-group-methods
209 methods/user-group-methods
@@ -1,144 +1,144 b''
1 .. _ssh-connection:
1 .. _ssh-connection:
2
2
3 SSH Connection
3 SSH Connection
4 --------------
4 --------------
5
5
6 If you wish to connect to your |repos| using SSH protocol, use the
6 If you wish to connect to your |repos| using SSH protocol, use the
7 following instructions.
7 following instructions.
8
8
9 1. Include |RCE| generated `authorized_keys` file into your sshd_config.
9 1. Include |RCE| generated `authorized_keys` file into your sshd_config.
10
10
11 By default a file `authorized_keys_rhodecode` is created containing
11 By default a file `authorized_keys_rhodecode` is created containing
12 configuration and all allowed user connection keys are stored inside.
12 configuration and all allowed user connection keys are stored inside.
13 On each change of stored keys inside |RCE| this file is updated with
13 On each change of stored keys inside |RCE| this file is updated with
14 proper data.
14 proper data.
15
15
16 .. code-block:: bash
16 .. code-block:: bash
17
17
18 # Edit sshd_config file most likely at /etc/ssh/sshd_config
18 # Edit sshd_config file most likely at /etc/ssh/sshd_config
19 # add or edit the AuthorizedKeysFile, and set to use custom files
19 # add or edit the AuthorizedKeysFile, and set to use custom files
20
20
21 AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
21 AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
22
22
23 This way we use a separate file for SSH access and separate one for
23 This way we use a separate file for SSH access and separate one for
24 SSH access to |RCE| repositories.
24 SSH access to |RCE| repositories.
25
25
26
26
27 2. Enable the SSH module on instance.
27 2. Enable the SSH module on instance.
28
28
29 On the server where |RCE| is running executing:
29 On the server where |RCE| is running executing:
30
30
31 .. code-block:: bash
31 .. code-block:: bash
32
32
33 rccontrol enable-module ssh {instance-id}
33 rccontrol enable-module ssh {instance-id}
34
34
35 This will add the following configuration into :file:`rhodecode.ini`.
35 This will add the following configuration into :file:`rhodecode.ini`.
36 This also can be done manually:
36 This also can be done manually:
37
37
38 .. code-block:: ini
38 .. code-block:: ini
39
39
40 ############################################################
40 ############################################################
41 ### SSH Support Settings ###
41 ### SSH Support Settings ###
42 ############################################################
42 ############################################################
43
43
44 ## Defines if a custom authorized_keys file should be created and written on
44 ## Defines if a custom authorized_keys file should be created and written on
45 ## any change user ssh keys. Setting this to false also disables posibility
45 ## any change user ssh keys. Setting this to false also disables posibility
46 ## of adding SSH keys by users from web interface. Super admins can still
46 ## of adding SSH keys by users from web interface. Super admins can still
47 ## manage SSH Keys.
47 ## manage SSH Keys.
48 ssh.generate_authorized_keyfile = true
48 ssh.generate_authorized_keyfile = true
49
49
50 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
50 ## Options for ssh, default is `no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding`
51 # ssh.authorized_keys_ssh_opts =
51 # ssh.authorized_keys_ssh_opts =
52
52
53 ## Path to the authrozied_keys file where the generate entries are placed.
53 ## Path to the authrozied_keys file where the generate entries are placed.
54 ## It is possible to have multiple key files specified in `sshd_config` e.g.
54 ## It is possible to have multiple key files specified in `sshd_config` e.g.
55 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
55 ## AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys_rhodecode
56 ssh.authorized_keys_file_path = ~/.ssh/authorized_keys_rhodecode
56 ssh.authorized_keys_file_path = ~/.ssh/authorized_keys_rhodecode
57
57
58 ## Command to execute the SSH wrapper. The binary is available in the
58 ## Command to execute the SSH wrapper. The binary is available in the
59 ## rhodecode installation directory.
59 ## rhodecode installation directory.
60 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
60 ## e.g ~/.rccontrol/community-1/profile/bin/rc-ssh-wrapper
61 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
61 ssh.wrapper_cmd = ~/.rccontrol/community-1/rc-ssh-wrapper
62
62
63 ## Allow shell when executing the ssh-wrapper command
63 ## Allow shell when executing the ssh-wrapper command
64 ssh.wrapper_cmd_allow_shell = false
64 ssh.wrapper_cmd_allow_shell = false
65
65
66 ## Enables logging, and detailed output send back to the client during SSH
66 ## Enables logging, and detailed output send back to the client during SSH
67 ## operations. Useful for debugging, shouldn't be used in production.
67 ## operations. Useful for debugging, shouldn't be used in production.
68 ssh.enable_debug_logging = false
68 ssh.enable_debug_logging = false
69
69
70 ## Paths to binary executable, by default they are the names, but we can
70 ## Paths to binary executable, by default they are the names, but we can
71 ## override them if we want to use a custom one
71 ## override them if we want to use a custom one
72 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
72 ssh.executable.hg = ~/.rccontrol/vcsserver-1/profile/bin/hg
73 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
73 ssh.executable.git = ~/.rccontrol/vcsserver-1/profile/bin/git
74 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
74 ssh.executable.svn = ~/.rccontrol/vcsserver-1/profile/bin/svnserve
75
75
76 ## Enables SSH key generator web interface. Disabling this still allows users
76 ## Enables SSH key generator web interface. Disabling this still allows users
77 ## to add their own keys.
77 ## to add their own keys.
78 ssh.enable_ui_key_generator = true
78 ssh.enable_ui_key_generator = true
79
79
80
80
81 3. Set base_url for instance to enable proper event handling (Optional):
81 3. Set base_url for instance to enable proper event handling (Optional):
82
82
83 If you wish to have integrations working correctly via SSH please configure
83 If you wish to have integrations working correctly via SSH please configure
84 The Application base_url.
84 The Application base_url.
85
85
86 Use the ``rccontrol status`` command to view instance details.
86 Use the ``rccontrol status`` command to view instance details.
87 Hostname is required for the integration to properly set the instance URL.
87 Hostname is required for the integration to properly set the instance URL.
88
88
89 When your hostname is known (e.g https://code.rhodecode.com) please set it
89 When your hostname is known (e.g https://code.rhodecode.com) please set it
90 inside :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
90 inside :file:`config/_shared/rhodecode.ini`
91
91
92 add into `[app:main]` section the following configuration:
92 add into `[app:main]` section the following configuration:
93
93
94 .. code-block:: ini
94 .. code-block:: ini
95
95
96 app.base_url = https://code.rhodecode.com
96 app.base_url = https://code.rhodecode.com
97
97
98
98
99 4. Add the public key to your user account for testing.
99 4. Add the public key to your user account for testing.
100 First generate a new key, or use your existing one and have your public key
100 First generate a new key, or use your existing one and have your public key
101 at hand.
101 at hand.
102
102
103 Go to
103 Go to
104 :menuselection:`My Account --> SSH Keys` and add the public key with proper description.
104 :menuselection:`My Account --> SSH Keys` and add the public key with proper description.
105
105
106 This will generate a new entry inside our configured `authorized_keys_rhodecode` file.
106 This will generate a new entry inside our configured `authorized_keys_rhodecode` file.
107
107
108 Test the connection from your local machine using the following example:
108 Test the connection from your local machine using the following example:
109
109
110 .. note::
110 .. note::
111
111
112 In case of connection problems please set
112 In case of connection problems please set
113 `ssh.enable_debug_logging = true` inside the SSH configuration of
113 `ssh.enable_debug_logging = true` inside the SSH configuration of
114 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
114 :file:`config/_shared/rhodecode.ini`
115 Then add, remove your SSH key and try connecting again.
115 Then add, remove your SSH key and try connecting again.
116 Debug logging will be printed to help find the problems on the server side.
116 Debug logging will be printed to help find the problems on the server side.
117
117
118 Test connection using the ssh command from the local machine. Make sure
118 Test connection using the ssh command from the local machine. Make sure
119 to use the use who is running the |RCE| server, and not your username from
119 to use the use who is running the |RCE| server, and not your username from
120 the web interface.
120 the web interface.
121
121
122
122
123 For SVN:
123 For SVN:
124
124
125 .. code-block:: bash
125 .. code-block:: bash
126
126
127 SVN_SSH="ssh -i ~/.ssh/id_rsa_test_ssh_private.key" svn checkout svn+ssh://rhodecode@rc-server/repo_name
127 SVN_SSH="ssh -i ~/.ssh/id_rsa_test_ssh_private.key" svn checkout svn+ssh://rhodecode@rc-server/repo_name
128
128
129 For GIT:
129 For GIT:
130
130
131 .. code-block:: bash
131 .. code-block:: bash
132
132
133 GIT_SSH_COMMAND='ssh -i ~/.ssh/id_rsa_test_ssh_private.key' git clone ssh://rhodecode@rc-server/repo_name
133 GIT_SSH_COMMAND='ssh -i ~/.ssh/id_rsa_test_ssh_private.key' git clone ssh://rhodecode@rc-server/repo_name
134
134
135 For Mercurial:
135 For Mercurial:
136
136
137 .. code-block:: bash
137 .. code-block:: bash
138
138
139 Add to hgrc:
139 Add to hgrc:
140
140
141 [ui]
141 [ui]
142 ssh = ssh -C -i ~/.ssh/id_rsa_test_ssh_private.key
142 ssh = ssh -C -i ~/.ssh/id_rsa_test_ssh_private.key
143
143
144 hg clone ssh://rhodecode@rc-server/repo_name
144 hg clone ssh://rhodecode@rc-server/repo_name
@@ -1,45 +1,45 b''
1 .. _set-up-mail:
1 .. _set-up-mail:
2
2
3 Set up Email
3 Set up Email
4 ------------
4 ------------
5
5
6 To setup email with your |RCE| instance, open the default
6 To setup email with your |RCE| instance, open the default
7 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini`
7 :file:`config/_shared/rhodecode.ini`
8 file and uncomment and configure the email section. If it is not there,
8 file and uncomment and configure the email section. If it is not there,
9 use the below example to insert it.
9 use the below example to insert it.
10
10
11 Once configured you can check the settings for your |RCE| instance on the
11 Once configured you can check the settings for your |RCE| instance on the
12 :menuselection:`Admin --> Settings --> Email` page.
12 :menuselection:`Admin --> Settings --> Email` page.
13
13
14 Please be aware that both section should be changed the `[DEFAULT]` for main applications
14 Please be aware that both section should be changed the `[DEFAULT]` for main applications
15 email config, and `[server:main]` for exception tracking email
15 email config, and `[server:main]` for exception tracking email
16
16
17 .. code-block:: ini
17 .. code-block:: ini
18
18
19 [DEFAULT]
19 [DEFAULT]
20 ; ########################################################################
20 ; ########################################################################
21 ; EMAIL CONFIGURATION
21 ; EMAIL CONFIGURATION
22 ; These settings will be used by the RhodeCode mailing system
22 ; These settings will be used by the RhodeCode mailing system
23 ; ########################################################################
23 ; ########################################################################
24
24
25 ; prefix all emails subjects with given prefix, helps filtering out emails
25 ; prefix all emails subjects with given prefix, helps filtering out emails
26 #email_prefix = [RhodeCode]
26 #email_prefix = [RhodeCode]
27
27
28 ; email FROM address all mails will be sent
28 ; email FROM address all mails will be sent
29 #app_email_from = rhodecode-noreply@localhost
29 #app_email_from = rhodecode-noreply@localhost
30
30
31 #smtp_server = mail.server.com
31 #smtp_server = mail.server.com
32 #smtp_username =
32 #smtp_username =
33 #smtp_password =
33 #smtp_password =
34 #smtp_port =
34 #smtp_port =
35 #smtp_use_tls = false
35 #smtp_use_tls = false
36 #smtp_use_ssl = true
36 #smtp_use_ssl = true
37
37
38 [server:main]
38 [server:main]
39 ; Send email with exception details when it happens
39 ; Send email with exception details when it happens
40 #exception_tracker.send_email = true
40 #exception_tracker.send_email = true
41
41
42 ; Comma separated list of recipients for exception emails,
42 ; Comma separated list of recipients for exception emails,
43 ; e.g admin@rhodecode.com,devops@rhodecode.com
43 ; e.g admin@rhodecode.com,devops@rhodecode.com
44 ; Can be left empty, then emails will be sent to ALL super-admins
44 ; Can be left empty, then emails will be sent to ALL super-admins
45 #exception_tracker.send_email_recipients =
45 #exception_tracker.send_email_recipients =
@@ -1,51 +1,52 b''
1 |RCE| 5.0.0 |RNS|
1 |RCE| 5.0.0 |RNS|
2 -----------------
2 -----------------
3
3
4 Release Date
4 Release Date
5 ^^^^^^^^^^^^
5 ^^^^^^^^^^^^
6
6
7 - TBA
7
8 - 2024-05-14
8
9
9
10
10 New Features
11 New Features
11 ^^^^^^^^^^^^
12 ^^^^^^^^^^^^
12
13
13 - Full support of Python3 and Python3.11
14 - Full support of Python3 and Python3.11
14 - Git repositories with LFS object are now pushing and pulling the LFS objects when remote sync is enabled.
15 - Git repositories with LFS object are now pushing and pulling the LFS objects when remote sync is enabled.
15 - Archive generation: implemented a new system for caching generated archive files that allows setting cache size limit
16 - Archive generation: implemented a new system for caching generated archive files that allows setting cache size limit see: archive_cache.cache_size_gb= option.
16 see: `archive_cache.cache_size_gb=` option.
17 - Introduced statsd metrics in various places for new monitoring stack to provide useful details on traffic and usage.
17 - Introduced statsd metrics in various places for new monitoring stack to provide useful details on traffic and usage.
18
18
19
19
20 General
20 General
21 ^^^^^^^
21 ^^^^^^^
22
22
23 - Upgraded all dependency libraries to their latest available versions
23 - Upgraded all dependency libraries to their latest available versions for python3 compatability
24 - Dropped support for deprecated hgsubversion no longer available in python3
25
24
26
25
27 Security
26 Security
28 ^^^^^^^^
27 ^^^^^^^^
29
28
29 - fixed few edge cases of permission invalidation on change of permissions
30
30
31
31
32 Performance
32 Performance
33 ^^^^^^^^^^^
33 ^^^^^^^^^^^
34
34
35 - Moved lot of dulwich based code into libgit2 implementation for better performance
35 - Moved lot of dulwich based code into libgit2 implementation for better performance
36
36
37
37
38 Fixes
38 Fixes
39 ^^^^^
39 ^^^^^
40
40
41 - Various small fixes and improvements found during python3 migration
41
42
42
43
43 Upgrade notes
44 Upgrade notes
44 ^^^^^^^^^^^^^
45 ^^^^^^^^^^^^^
45
46
46 - RhodeCode 5.0.0 is a mayor upgrade that support latest Python versions 3.11,
47 - RhodeCode 5.0.0 is a mayor upgrade that support latest Python versions 3.11,
47 and also comes with lots of internal changes and performance improvements.
48 and also comes with lots of internal changes and performance improvements.
48 - RhodeCode 5.0.0 is only supported via our new docker rcstack installer or helm/k8s stack
49 - RhodeCode 5.0.0 is only supported via our new docker rcstack installer or helm/k8s stack
49 - Please see migration guide :ref:`rcstack:migration-to-rcstack`
50 - Please see migration guide :ref:`rcstack:migration-to-rcstack`
50 - RhodeCode 5.0.0 also requires a new license for EE instances, please reach out to us via support@rhodecode.com to
51 - RhodeCode 5.0.0 also requires a new license for EE instances, please reach out to us via support@rhodecode.com to
51 convert your license key.
52 convert your license key.
@@ -1,168 +1,173 b''
1 .. _rhodecode-release-notes-ref:
1 .. _rhodecode-release-notes-ref:
2
2
3 Release Notes
3 Release Notes
4 =============
4 =============
5
5
6 |RCE| 5.x Versions
6 |RCE| 5.x Versions
7 ------------------
7 ------------------
8
8
9 .. toctree::
9 .. toctree::
10 :maxdepth: 1
10 :maxdepth: 1
11
11
12
13 release-notes-5.1.0.rst
14 release-notes-5.0.3.rst
15 release-notes-5.0.2.rst
16 release-notes-5.0.1.rst
12 release-notes-5.0.0.rst
17 release-notes-5.0.0.rst
13
18
14
19
15 |RCE| 4.x Versions
20 |RCE| 4.x Versions
16 ------------------
21 ------------------
17
22
18 .. toctree::
23 .. toctree::
19 :maxdepth: 1
24 :maxdepth: 1
20
25
21 release-notes-4.27.1.rst
26 release-notes-4.27.1.rst
22 release-notes-4.27.0.rst
27 release-notes-4.27.0.rst
23 release-notes-4.26.0.rst
28 release-notes-4.26.0.rst
24 release-notes-4.25.2.rst
29 release-notes-4.25.2.rst
25 release-notes-4.25.1.rst
30 release-notes-4.25.1.rst
26 release-notes-4.25.0.rst
31 release-notes-4.25.0.rst
27 release-notes-4.24.1.rst
32 release-notes-4.24.1.rst
28 release-notes-4.24.0.rst
33 release-notes-4.24.0.rst
29 release-notes-4.23.2.rst
34 release-notes-4.23.2.rst
30 release-notes-4.23.1.rst
35 release-notes-4.23.1.rst
31 release-notes-4.23.0.rst
36 release-notes-4.23.0.rst
32 release-notes-4.22.0.rst
37 release-notes-4.22.0.rst
33 release-notes-4.21.0.rst
38 release-notes-4.21.0.rst
34 release-notes-4.20.1.rst
39 release-notes-4.20.1.rst
35 release-notes-4.20.0.rst
40 release-notes-4.20.0.rst
36 release-notes-4.19.3.rst
41 release-notes-4.19.3.rst
37 release-notes-4.19.2.rst
42 release-notes-4.19.2.rst
38 release-notes-4.19.1.rst
43 release-notes-4.19.1.rst
39 release-notes-4.19.0.rst
44 release-notes-4.19.0.rst
40 release-notes-4.18.3.rst
45 release-notes-4.18.3.rst
41 release-notes-4.18.2.rst
46 release-notes-4.18.2.rst
42 release-notes-4.18.1.rst
47 release-notes-4.18.1.rst
43 release-notes-4.18.0.rst
48 release-notes-4.18.0.rst
44 release-notes-4.17.4.rst
49 release-notes-4.17.4.rst
45 release-notes-4.17.3.rst
50 release-notes-4.17.3.rst
46 release-notes-4.17.2.rst
51 release-notes-4.17.2.rst
47 release-notes-4.17.1.rst
52 release-notes-4.17.1.rst
48 release-notes-4.17.0.rst
53 release-notes-4.17.0.rst
49 release-notes-4.16.2.rst
54 release-notes-4.16.2.rst
50 release-notes-4.16.1.rst
55 release-notes-4.16.1.rst
51 release-notes-4.16.0.rst
56 release-notes-4.16.0.rst
52 release-notes-4.15.2.rst
57 release-notes-4.15.2.rst
53 release-notes-4.15.1.rst
58 release-notes-4.15.1.rst
54 release-notes-4.15.0.rst
59 release-notes-4.15.0.rst
55 release-notes-4.14.1.rst
60 release-notes-4.14.1.rst
56 release-notes-4.14.0.rst
61 release-notes-4.14.0.rst
57 release-notes-4.13.3.rst
62 release-notes-4.13.3.rst
58 release-notes-4.13.2.rst
63 release-notes-4.13.2.rst
59 release-notes-4.13.1.rst
64 release-notes-4.13.1.rst
60 release-notes-4.13.0.rst
65 release-notes-4.13.0.rst
61 release-notes-4.12.4.rst
66 release-notes-4.12.4.rst
62 release-notes-4.12.3.rst
67 release-notes-4.12.3.rst
63 release-notes-4.12.2.rst
68 release-notes-4.12.2.rst
64 release-notes-4.12.1.rst
69 release-notes-4.12.1.rst
65 release-notes-4.12.0.rst
70 release-notes-4.12.0.rst
66 release-notes-4.11.6.rst
71 release-notes-4.11.6.rst
67 release-notes-4.11.5.rst
72 release-notes-4.11.5.rst
68 release-notes-4.11.4.rst
73 release-notes-4.11.4.rst
69 release-notes-4.11.3.rst
74 release-notes-4.11.3.rst
70 release-notes-4.11.2.rst
75 release-notes-4.11.2.rst
71 release-notes-4.11.1.rst
76 release-notes-4.11.1.rst
72 release-notes-4.11.0.rst
77 release-notes-4.11.0.rst
73 release-notes-4.10.6.rst
78 release-notes-4.10.6.rst
74 release-notes-4.10.5.rst
79 release-notes-4.10.5.rst
75 release-notes-4.10.4.rst
80 release-notes-4.10.4.rst
76 release-notes-4.10.3.rst
81 release-notes-4.10.3.rst
77 release-notes-4.10.2.rst
82 release-notes-4.10.2.rst
78 release-notes-4.10.1.rst
83 release-notes-4.10.1.rst
79 release-notes-4.10.0.rst
84 release-notes-4.10.0.rst
80 release-notes-4.9.1.rst
85 release-notes-4.9.1.rst
81 release-notes-4.9.0.rst
86 release-notes-4.9.0.rst
82 release-notes-4.8.0.rst
87 release-notes-4.8.0.rst
83 release-notes-4.7.2.rst
88 release-notes-4.7.2.rst
84 release-notes-4.7.1.rst
89 release-notes-4.7.1.rst
85 release-notes-4.7.0.rst
90 release-notes-4.7.0.rst
86 release-notes-4.6.1.rst
91 release-notes-4.6.1.rst
87 release-notes-4.6.0.rst
92 release-notes-4.6.0.rst
88 release-notes-4.5.2.rst
93 release-notes-4.5.2.rst
89 release-notes-4.5.1.rst
94 release-notes-4.5.1.rst
90 release-notes-4.5.0.rst
95 release-notes-4.5.0.rst
91 release-notes-4.4.2.rst
96 release-notes-4.4.2.rst
92 release-notes-4.4.1.rst
97 release-notes-4.4.1.rst
93 release-notes-4.4.0.rst
98 release-notes-4.4.0.rst
94 release-notes-4.3.1.rst
99 release-notes-4.3.1.rst
95 release-notes-4.3.0.rst
100 release-notes-4.3.0.rst
96 release-notes-4.2.1.rst
101 release-notes-4.2.1.rst
97 release-notes-4.2.0.rst
102 release-notes-4.2.0.rst
98 release-notes-4.1.2.rst
103 release-notes-4.1.2.rst
99 release-notes-4.1.1.rst
104 release-notes-4.1.1.rst
100 release-notes-4.1.0.rst
105 release-notes-4.1.0.rst
101 release-notes-4.0.1.rst
106 release-notes-4.0.1.rst
102 release-notes-4.0.0.rst
107 release-notes-4.0.0.rst
103
108
104 |RCE| 3.x Versions
109 |RCE| 3.x Versions
105 ------------------
110 ------------------
106
111
107 .. toctree::
112 .. toctree::
108 :maxdepth: 1
113 :maxdepth: 1
109
114
110 release-notes-3.8.4.rst
115 release-notes-3.8.4.rst
111 release-notes-3.8.3.rst
116 release-notes-3.8.3.rst
112 release-notes-3.8.2.rst
117 release-notes-3.8.2.rst
113 release-notes-3.8.1.rst
118 release-notes-3.8.1.rst
114 release-notes-3.8.0.rst
119 release-notes-3.8.0.rst
115 release-notes-3.7.1.rst
120 release-notes-3.7.1.rst
116 release-notes-3.7.0.rst
121 release-notes-3.7.0.rst
117 release-notes-3.6.1.rst
122 release-notes-3.6.1.rst
118 release-notes-3.6.0.rst
123 release-notes-3.6.0.rst
119 release-notes-3.5.2.rst
124 release-notes-3.5.2.rst
120 release-notes-3.5.1.rst
125 release-notes-3.5.1.rst
121 release-notes-3.5.0.rst
126 release-notes-3.5.0.rst
122 release-notes-3.4.1.rst
127 release-notes-3.4.1.rst
123 release-notes-3.4.0.rst
128 release-notes-3.4.0.rst
124 release-notes-3.3.4.rst
129 release-notes-3.3.4.rst
125 release-notes-3.3.3.rst
130 release-notes-3.3.3.rst
126 release-notes-3.3.2.rst
131 release-notes-3.3.2.rst
127 release-notes-3.3.1.rst
132 release-notes-3.3.1.rst
128 release-notes-3.3.0.rst
133 release-notes-3.3.0.rst
129 release-notes-3.2.3.rst
134 release-notes-3.2.3.rst
130 release-notes-3.2.2.rst
135 release-notes-3.2.2.rst
131 release-notes-3.2.1.rst
136 release-notes-3.2.1.rst
132 release-notes-3.2.0.rst
137 release-notes-3.2.0.rst
133 release-notes-3.1.1.rst
138 release-notes-3.1.1.rst
134 release-notes-3.1.0.rst
139 release-notes-3.1.0.rst
135 release-notes-3.0.2.rst
140 release-notes-3.0.2.rst
136 release-notes-3.0.1.rst
141 release-notes-3.0.1.rst
137 release-notes-3.0.0.rst
142 release-notes-3.0.0.rst
138
143
139 |RCE| 2.x Versions
144 |RCE| 2.x Versions
140 ------------------
145 ------------------
141
146
142 .. toctree::
147 .. toctree::
143 :maxdepth: 1
148 :maxdepth: 1
144
149
145 release-notes-2.2.8.rst
150 release-notes-2.2.8.rst
146 release-notes-2.2.7.rst
151 release-notes-2.2.7.rst
147 release-notes-2.2.6.rst
152 release-notes-2.2.6.rst
148 release-notes-2.2.5.rst
153 release-notes-2.2.5.rst
149 release-notes-2.2.4.rst
154 release-notes-2.2.4.rst
150 release-notes-2.2.3.rst
155 release-notes-2.2.3.rst
151 release-notes-2.2.2.rst
156 release-notes-2.2.2.rst
152 release-notes-2.2.1.rst
157 release-notes-2.2.1.rst
153 release-notes-2.2.0.rst
158 release-notes-2.2.0.rst
154 release-notes-2.1.0.rst
159 release-notes-2.1.0.rst
155 release-notes-2.0.2.rst
160 release-notes-2.0.2.rst
156 release-notes-2.0.1.rst
161 release-notes-2.0.1.rst
157 release-notes-2.0.0.rst
162 release-notes-2.0.0.rst
158
163
159 |RCE| 1.x Versions
164 |RCE| 1.x Versions
160 ------------------
165 ------------------
161
166
162 .. toctree::
167 .. toctree::
163 :maxdepth: 1
168 :maxdepth: 1
164
169
165 release-notes-1.7.2.rst
170 release-notes-1.7.2.rst
166 release-notes-1.7.1.rst
171 release-notes-1.7.1.rst
167 release-notes-1.7.0.rst
172 release-notes-1.7.0.rst
168 release-notes-1.6.0.rst
173 release-notes-1.6.0.rst
@@ -1,91 +1,91 b''
1 .. _multi-instance-setup:
1 .. _multi-instance-setup:
2
2
3 Scaling |RCE| Using Multiple Instances
3 Scaling |RCE| Using Multiple Instances
4 ======================================
4 ======================================
5
5
6 Running multiple instances of |RCE| from a single database can be used to
6 Running multiple instances of |RCE| from a single database can be used to
7 scale the application for the following deployment setups:
7 scale the application for the following deployment setups:
8
8
9 * Using dedicated Continuous Integrations instances.
9 * Using dedicated Continuous Integrations instances.
10 * Locating instances closer to geographically dispersed development teams.
10 * Locating instances closer to geographically dispersed development teams.
11 * Running production and testing instances, or failover instances on a
11 * Running production and testing instances, or failover instances on a
12 different server.
12 different server.
13 * Running proxy read-only instances for pull operations.
13 * Running proxy read-only instances for pull operations.
14
14
15 If you wish to run multiple instances of |RCE| using a single database for
15 If you wish to run multiple instances of |RCE| using a single database for
16 settings, use the following instructions to set this up. Before you get onto
16 settings, use the following instructions to set this up. Before you get onto
17 multiple instances though, you should install |RCE|, and set
17 multiple instances though, you should install |RCE|, and set
18 up your first instance as you see fit. You can see the full instructions here
18 up your first instance as you see fit. You can see the full instructions here
19 :ref:`Installing RhodeCode Enterprise <control:rcc>`
19 :ref:`Installing RhodeCode Enterprise <control:rcc>`
20
20
21 Once you have configured your first instance, you can run additional instances
21 Once you have configured your first instance, you can run additional instances
22 from the same database using the following steps:
22 from the same database using the following steps:
23
23
24 1. Install a new instance of |RCE|, choosing SQLite as the database. It is
24 1. Install a new instance of |RCE|, choosing SQLite as the database. It is
25 important to choose SQLite, because this will not overwrite any other
25 important to choose SQLite, because this will not overwrite any other
26 database settings you may have.
26 database settings you may have.
27
27
28 Once the new instance is installed you need to update the licence token and
28 Once the new instance is installed you need to update the licence token and
29 database connection string in the
29 database connection string in the
30 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file.
30 :file:`config/_shared/rhodecode.ini` file.
31
31
32 .. code-block:: bash
32 .. code-block:: bash
33
33
34 $ rccontrol install Enterprise
34 $ rccontrol install Enterprise
35
35
36 Agree to the licence agreement? [y/N]: y
36 Agree to the licence agreement? [y/N]: y
37 Username [admin]: username
37 Username [admin]: username
38 Password (min 6 chars):
38 Password (min 6 chars):
39 Repeat for confirmation:
39 Repeat for confirmation:
40 Email: user@example.com
40 Email: user@example.com
41 Respositories location [/home/brian/repos]:
41 Respositories location [/home/brian/repos]:
42 IP to start the Enterprise server on [127.0.0.1]:
42 IP to start the Enterprise server on [127.0.0.1]:
43 Port for the Enterprise server to use [10000]:
43 Port for the Enterprise server to use [10000]:
44 Database type - [s]qlite, [m]ysql, [p]ostresql: s
44 Database type - [s]qlite, [m]ysql, [p]ostresql: s
45
45
46 2. The licence token used on each new instance needs to be the token from your
46 2. The licence token used on each new instance needs to be the token from your
47 initial instance. This allows multiple instances to run the same licence key.
47 initial instance. This allows multiple instances to run the same licence key.
48
48
49 To get the licence token, go to the |RCE| interface of your primary
49 To get the licence token, go to the |RCE| interface of your primary
50 instance and select :menuselection:`admin --> setting --> license`. Then
50 instance and select :menuselection:`admin --> setting --> license`. Then
51 update the licence token setting in each new instance's
51 update the licence token setting in each new instance's
52 :file:`rhodecode.ini` file.
52 :file:`rhodecode.ini` file.
53
53
54 .. code-block:: ini
54 .. code-block:: ini
55
55
56 ## generated license token, goto license page in RhodeCode settings to get
56 ## generated license token, goto license page in RhodeCode settings to get
57 ## new token
57 ## new token
58 license_token = add-token-here
58 license_token = add-token-here
59
59
60 3. Update the database connection string in the
60 3. Update the database connection string in the
61 :file:`rhodecode.ini` file to point to your database. For
61 :file:`rhodecode.ini` file to point to your database. For
62 more information, see :ref:`config-database`.
62 more information, see :ref:`config-database`.
63
63
64 .. code-block:: ini
64 .. code-block:: ini
65
65
66 #########################################################
66 #########################################################
67 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
67 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
68 #########################################################
68 #########################################################
69
69
70 # Default SQLite config
70 # Default SQLite config
71 sqlalchemy.db1.url = sqlite:////home/user/.rccontrol/enterprise-1/rhodecode.db
71 sqlalchemy.db1.url = sqlite:////home/user/.rccontrol/enterprise-1/rhodecode.db
72
72
73 # Use this example for a PostgreSQL
73 # Use this example for a PostgreSQL
74 sqlalchemy.db1.url = postgresql://username:password@localhost/rhodecode
74 sqlalchemy.db1.url = postgresql://username:password@localhost/rhodecode
75
75
76 4. Restart your updated instance. Once restarted the new instance will read
76 4. Restart your updated instance. Once restarted the new instance will read
77 the licence key in the database and will function identically as the
77 the licence key in the database and will function identically as the
78 original instance.
78 original instance.
79
79
80 .. code-block:: bash
80 .. code-block:: bash
81
81
82 $ rccontrol restart enterprise-2
82 $ rccontrol restart enterprise-2
83
83
84 If you wish to add additional performance to your setup, see the
84 If you wish to add additional performance to your setup, see the
85 :ref:`rhodecode-tuning-ref` section.
85 :ref:`rhodecode-tuning-ref` section.
86
86
87 Scaling Deployment Diagram
87 Scaling Deployment Diagram
88 --------------------------
88 --------------------------
89
89
90 .. image:: ../images/scaling-diagrm.png
90 .. image:: ../images/scaling-diagrm.png
91 :align: center
91 :align: center
@@ -1,61 +1,60 b''
1 {
1 {
2 "name": "rhodecode-enterprise",
2 "name": "rhodecode-enterprise",
3 "version": "5.0.0",
3 "version": "5.0.0",
4 "private": true,
4 "private": true,
5 "description": "RhodeCode JS packaged",
5 "description": "RhodeCode JS packaged",
6 "license": "SEE LICENSE IN LICENSE.txt",
6 "license": "SEE LICENSE IN LICENSE.txt",
7 "repository": {
7 "repository": {
8 "type": "hg",
8 "type": "hg",
9 "url": "https://code.rhodecode.com/rhodecode-enterprise-ce"
9 "url": "https://code.rhodecode.com/rhodecode-enterprise-ce"
10 },
10 },
11 "devDependencies": {
11 "devDependencies": {
12 "@polymer/iron-a11y-keys": "^3.0.0",
12 "@polymer/iron-a11y-keys": "^3.0.0",
13 "@polymer/iron-ajax": "^3.0.0",
13 "@polymer/iron-ajax": "^3.0.0",
14 "@polymer/iron-autogrow-textarea": "^3.0.0",
14 "@polymer/iron-autogrow-textarea": "^3.0.0",
15 "@polymer/paper-button": "^3.0.0",
15 "@polymer/paper-button": "^3.0.0",
16 "@polymer/paper-spinner": "^3.0.0",
16 "@polymer/paper-spinner": "^3.0.0",
17 "@polymer/paper-toast": "^3.0.0",
17 "@polymer/paper-toast": "^3.0.0",
18 "@polymer/paper-toggle-button": "^3.0.0",
18 "@polymer/paper-toggle-button": "^3.0.0",
19 "@polymer/paper-tooltip": "^3.0.0",
19 "@polymer/paper-tooltip": "^3.0.0",
20 "@polymer/polymer": "^3.0.0",
20 "@polymer/polymer": "^3.0.0",
21 "@webcomponents/webcomponentsjs": "^2.0.0",
21 "@webcomponents/webcomponentsjs": "^2.0.0",
22 "babel-core": "^6.26.3",
22 "babel-core": "^6.26.3",
23 "babel-loader": "^7.1.2",
23 "babel-loader": "^7.1.2",
24 "babel-plugin-transform-object-rest-spread": "^6.26.0",
24 "babel-plugin-transform-object-rest-spread": "^6.26.0",
25 "babel-preset-env": "^1.6.0",
25 "babel-preset-env": "^1.6.0",
26 "clipboard": "^2.0.1",
26 "clipboard": "^2.0.1",
27 "copy-webpack-plugin": "^4.4.2",
27 "copy-webpack-plugin": "^4.4.2",
28 "css-loader": "^0.28.11",
28 "css-loader": "^0.28.11",
29 "dropzone": "^5.5.0",
29 "dropzone": "^5.5.0",
30 "exports-loader": "^0.6.4",
30 "exports-loader": "^0.6.4",
31 "favico.js": "^0.3.10",
31 "favico.js": "^0.3.10",
32 "grunt": "^0.4.5",
32 "grunt": "^0.4.5",
33 "grunt-cli": "^1.4.3",
33 "grunt-cli": "^1.4.3",
34 "grunt-contrib-concat": "^0.5.1",
34 "grunt-contrib-concat": "^0.5.1",
35 "grunt-contrib-copy": "^1.0.0",
35 "grunt-contrib-copy": "^1.0.0",
36 "grunt-contrib-jshint": "^0.12.0",
36 "grunt-contrib-jshint": "^0.12.0",
37 "grunt-contrib-less": "^1.1.0",
37 "grunt-contrib-less": "^1.1.0",
38 "grunt-contrib-uglify": "^4.0.1",
38 "grunt-contrib-uglify": "^4.0.1",
39 "grunt-contrib-watch": "^0.6.1",
39 "grunt-contrib-watch": "^0.6.1",
40 "grunt-webpack": "^3.1.3",
40 "grunt-webpack": "^3.1.3",
41 "html-loader": "^0.4.4",
41 "html-loader": "^0.4.4",
42 "html-webpack-plugin": "^3.2.0",
42 "html-webpack-plugin": "^3.2.0",
43 "imports-loader": "^0.7.1",
43 "imports-loader": "^0.7.1",
44 "jquery": "1.11.3",
44 "jquery": "1.11.3",
45 "jshint": "^2.9.1-rc3",
45 "jshint": "^2.9.1-rc3",
46 "mark.js": "8.11.1",
46 "mark.js": "8.11.1",
47 "moment": "^2.18.1",
47 "moment": "^2.18.1",
48 "mousetrap": "^1.6.1",
48 "mousetrap": "^1.6.1",
49 "polymer-webpack-loader": "^2.0.1",
49 "polymer-webpack-loader": "^2.0.1",
50 "qrious": "^4.0.2",
51 "raw-loader": "1.0.0-beta.0",
50 "raw-loader": "1.0.0-beta.0",
52 "sticky-sidebar": "3.3.1",
51 "sticky-sidebar": "3.3.1",
53 "style-loader": "^0.21.0",
52 "style-loader": "^0.21.0",
54 "sweetalert2": "^9.10.12",
53 "sweetalert2": "^9.10.12",
55 "ts-loader": "^1.3.3",
54 "ts-loader": "^1.3.3",
56 "waypoints": "4.0.1",
55 "waypoints": "4.0.1",
57 "webpack": "4.23.1",
56 "webpack": "4.23.1",
58 "webpack-cli": "3.1.2",
57 "webpack-cli": "3.1.2",
59 "webpack-uglify-js-plugin": "^1.1.9"
58 "webpack-uglify-js-plugin": "^1.1.9"
60 }
59 }
61 }
60 }
@@ -1,23 +1,27 b''
1 [pytest]
1 [pytest]
2 testpaths = rhodecode
2 testpaths = rhodecode
3 norecursedirs = rhodecode/public rhodecode/templates tests/scripts
3 norecursedirs = rhodecode/public rhodecode/templates tests/scripts
4 cache_dir = /tmp/.pytest_cache
4 cache_dir = /tmp/.pytest_cache
5
5
6 pyramid_config = rhodecode/tests/rhodecode.ini
6 pyramid_config = rhodecode/tests/rhodecode.ini
7 vcsserver_protocol = http
7 vcsserver_protocol = http
8 vcsserver_config_http = rhodecode/tests/vcsserver_http.ini
8 vcsserver_config_http = rhodecode/tests/vcsserver_http.ini
9
9
10 addopts =
10 addopts =
11 --pdbcls=IPython.terminal.debugger:TerminalPdb
11 --pdbcls=IPython.terminal.debugger:TerminalPdb
12 --strict-markers
12 --strict-markers
13 --capture=no
13 --capture=no
14 --show-capture=all
14 --show-capture=all
15
15
16 # --test-loglevel=INFO, show log-level during execution
16 # --test-loglevel=INFO, show log-level during execution
17
17
18 markers =
18 markers =
19 vcs_operations: Mark tests depending on a running RhodeCode instance.
19 vcs_operations: Mark tests depending on a running RhodeCode instance.
20 xfail_backends: Mark tests as xfail for given backends.
20 xfail_backends: Mark tests as xfail for given backends.
21 skip_backends: Mark tests as skipped for given backends.
21 skip_backends: Mark tests as skipped for given backends.
22 backends: Mark backends
22 backends: Mark backends
23 dbs: database markers for running tests for given DB
23 dbs: database markers for running tests for given DB
24
25 env =
26 RC_TEST=1
27 RUN_ENV=test
@@ -1,295 +1,313 b''
1 # deps, generated via pipdeptree --exclude setuptools,wheel,pipdeptree,pip -f | tr '[:upper:]' '[:lower:]'
1 # deps, generated via pipdeptree --exclude setuptools,wheel,pipdeptree,pip -f | tr '[:upper:]' '[:lower:]'
2
2
3 alembic==1.12.1
3 alembic==1.13.1
4 mako==1.2.4
4 mako==1.2.4
5 markupsafe==2.1.2
5 markupsafe==2.1.2
6 sqlalchemy==1.4.51
6 sqlalchemy==1.4.52
7 greenlet==3.0.3
7 greenlet==3.0.3
8 typing_extensions==4.9.0
8 typing_extensions==4.9.0
9 async-timeout==4.0.3
9 async-timeout==4.0.3
10 babel==2.12.1
10 babel==2.12.1
11 beaker==1.12.1
11 beaker==1.12.1
12 celery==5.3.6
12 celery==5.3.6
13 billiard==4.2.0
13 billiard==4.2.0
14 click==8.1.3
14 click==8.1.3
15 click-didyoumean==0.3.0
15 click-didyoumean==0.3.0
16 click==8.1.3
16 click==8.1.3
17 click-plugins==1.1.1
17 click-plugins==1.1.1
18 click==8.1.3
18 click==8.1.3
19 click-repl==0.2.0
19 click-repl==0.2.0
20 click==8.1.3
20 click==8.1.3
21 prompt-toolkit==3.0.38
21 prompt-toolkit==3.0.38
22 wcwidth==0.2.6
22 wcwidth==0.2.6
23 six==1.16.0
23 six==1.16.0
24 kombu==5.3.5
24 kombu==5.3.5
25 amqp==5.2.0
25 amqp==5.2.0
26 vine==5.1.0
26 vine==5.1.0
27 vine==5.1.0
27 vine==5.1.0
28 python-dateutil==2.8.2
28 python-dateutil==2.8.2
29 six==1.16.0
29 six==1.16.0
30 tzdata==2023.4
30 tzdata==2024.1
31 vine==5.1.0
31 vine==5.1.0
32 channelstream==0.7.1
32 channelstream==0.7.1
33 gevent==24.2.1
33 gevent==24.2.1
34 greenlet==3.0.3
34 greenlet==3.0.3
35 zope.event==5.0.0
35 zope.event==5.0.0
36 zope.interface==6.1.0
36 zope.interface==6.3.0
37 itsdangerous==1.1.0
37 itsdangerous==1.1.0
38 marshmallow==2.18.0
38 marshmallow==2.18.0
39 pyramid==2.0.2
39 pyramid==2.0.2
40 hupper==1.12
40 hupper==1.12
41 plaster==1.1.2
41 plaster==1.1.2
42 plaster-pastedeploy==1.0.1
42 plaster-pastedeploy==1.0.1
43 pastedeploy==3.1.0
43 pastedeploy==3.1.0
44 plaster==1.1.2
44 plaster==1.1.2
45 translationstring==1.4
45 translationstring==1.4
46 venusian==3.0.0
46 venusian==3.0.0
47 webob==1.8.7
47 webob==1.8.7
48 zope.deprecation==5.0.0
48 zope.deprecation==5.0.0
49 zope.interface==6.1.0
49 zope.interface==6.3.0
50 pyramid-apispec==0.3.3
51 apispec==1.3.3
52 pyramid-jinja2==2.10
50 pyramid-jinja2==2.10
53 jinja2==3.1.2
51 jinja2==3.1.2
54 markupsafe==2.1.2
52 markupsafe==2.1.2
55 markupsafe==2.1.2
53 markupsafe==2.1.2
56 pyramid==2.0.2
54 pyramid==2.0.2
57 hupper==1.12
55 hupper==1.12
58 plaster==1.1.2
56 plaster==1.1.2
59 plaster-pastedeploy==1.0.1
57 plaster-pastedeploy==1.0.1
60 pastedeploy==3.1.0
58 pastedeploy==3.1.0
61 plaster==1.1.2
59 plaster==1.1.2
62 translationstring==1.4
60 translationstring==1.4
63 venusian==3.0.0
61 venusian==3.0.0
64 webob==1.8.7
62 webob==1.8.7
65 zope.deprecation==5.0.0
63 zope.deprecation==5.0.0
66 zope.interface==6.1.0
64 zope.interface==6.3.0
67 zope.deprecation==5.0.0
65 zope.deprecation==5.0.0
68 python-dateutil==2.8.2
66 python-dateutil==2.8.2
69 six==1.16.0
67 six==1.16.0
70 requests==2.28.2
68 requests==2.28.2
71 certifi==2022.12.7
69 certifi==2022.12.7
72 charset-normalizer==3.1.0
70 charset-normalizer==3.1.0
73 idna==3.4
71 idna==3.4
74 urllib3==1.26.14
72 urllib3==1.26.14
75 ws4py==0.5.1
73 ws4py==0.5.1
76 deform==2.0.15
74 deform==2.0.15
77 chameleon==3.10.2
75 chameleon==3.10.2
78 colander==2.0
76 colander==2.0
79 iso8601==1.1.0
77 iso8601==1.1.0
80 translationstring==1.4
78 translationstring==1.4
81 iso8601==1.1.0
79 iso8601==1.1.0
82 peppercorn==0.6
80 peppercorn==0.6
83 translationstring==1.4
81 translationstring==1.4
84 zope.deprecation==5.0.0
82 zope.deprecation==5.0.0
85 diskcache==5.6.3
86 docutils==0.19
83 docutils==0.19
87 dogpile.cache==1.3.0
84 dogpile.cache==1.3.3
88 decorator==5.1.1
85 decorator==5.1.1
89 stevedore==5.1.0
86 stevedore==5.1.0
90 pbr==5.11.1
87 pbr==5.11.1
91 formencode==2.1.0
88 formencode==2.1.0
92 six==1.16.0
89 six==1.16.0
90 fsspec==2024.6.0
93 gunicorn==21.2.0
91 gunicorn==21.2.0
94 packaging==23.1
92 packaging==24.0
95 gevent==24.2.1
93 gevent==24.2.1
96 greenlet==3.0.3
94 greenlet==3.0.3
97 zope.event==5.0.0
95 zope.event==5.0.0
98 zope.interface==6.1.0
96 zope.interface==6.3.0
99 ipython==8.14.0
97 ipython==8.14.0
100 backcall==0.2.0
98 backcall==0.2.0
101 decorator==5.1.1
99 decorator==5.1.1
102 jedi==0.19.0
100 jedi==0.19.0
103 parso==0.8.3
101 parso==0.8.3
104 matplotlib-inline==0.1.6
102 matplotlib-inline==0.1.6
105 traitlets==5.9.0
103 traitlets==5.9.0
106 pexpect==4.8.0
104 pexpect==4.8.0
107 ptyprocess==0.7.0
105 ptyprocess==0.7.0
108 pickleshare==0.7.5
106 pickleshare==0.7.5
109 prompt-toolkit==3.0.38
107 prompt-toolkit==3.0.38
110 wcwidth==0.2.6
108 wcwidth==0.2.6
111 pygments==2.15.1
109 pygments==2.15.1
112 stack-data==0.6.2
110 stack-data==0.6.2
113 asttokens==2.2.1
111 asttokens==2.2.1
114 six==1.16.0
112 six==1.16.0
115 executing==1.2.0
113 executing==1.2.0
116 pure-eval==0.2.2
114 pure-eval==0.2.2
117 traitlets==5.9.0
115 traitlets==5.9.0
118 markdown==3.4.3
116 markdown==3.4.3
119 msgpack==1.0.7
117 msgpack==1.0.8
120 mysqlclient==2.1.1
118 mysqlclient==2.1.1
121 nbconvert==7.7.3
119 nbconvert==7.7.3
122 beautifulsoup4==4.11.2
120 beautifulsoup4==4.12.3
123 soupsieve==2.4
121 soupsieve==2.5
124 bleach==6.1.0
122 bleach==6.1.0
125 six==1.16.0
123 six==1.16.0
126 webencodings==0.5.1
124 webencodings==0.5.1
127 defusedxml==0.7.1
125 defusedxml==0.7.1
128 jinja2==3.1.2
126 jinja2==3.1.2
129 markupsafe==2.1.2
127 markupsafe==2.1.2
130 jupyter_core==5.3.1
128 jupyter_core==5.3.1
131 platformdirs==3.10.0
129 platformdirs==3.10.0
132 traitlets==5.9.0
130 traitlets==5.9.0
133 jupyterlab-pygments==0.2.2
131 jupyterlab-pygments==0.2.2
134 markupsafe==2.1.2
132 markupsafe==2.1.2
135 mistune==2.0.5
133 mistune==2.0.5
136 nbclient==0.8.0
134 nbclient==0.8.0
137 jupyter_client==8.3.0
135 jupyter_client==8.3.0
138 jupyter_core==5.3.1
136 jupyter_core==5.3.1
139 platformdirs==3.10.0
137 platformdirs==3.10.0
140 traitlets==5.9.0
138 traitlets==5.9.0
141 python-dateutil==2.8.2
139 python-dateutil==2.8.2
142 six==1.16.0
140 six==1.16.0
143 pyzmq==25.0.0
141 pyzmq==25.0.0
144 tornado==6.2
142 tornado==6.2
145 traitlets==5.9.0
143 traitlets==5.9.0
146 jupyter_core==5.3.1
144 jupyter_core==5.3.1
147 platformdirs==3.10.0
145 platformdirs==3.10.0
148 traitlets==5.9.0
146 traitlets==5.9.0
149 nbformat==5.9.2
147 nbformat==5.9.2
150 fastjsonschema==2.18.0
148 fastjsonschema==2.18.0
151 jsonschema==4.18.6
149 jsonschema==4.18.6
152 attrs==22.2.0
150 attrs==22.2.0
153 pyrsistent==0.19.3
151 pyrsistent==0.19.3
154 jupyter_core==5.3.1
152 jupyter_core==5.3.1
155 platformdirs==3.10.0
153 platformdirs==3.10.0
156 traitlets==5.9.0
154 traitlets==5.9.0
157 traitlets==5.9.0
155 traitlets==5.9.0
158 traitlets==5.9.0
156 traitlets==5.9.0
159 nbformat==5.9.2
157 nbformat==5.9.2
160 fastjsonschema==2.18.0
158 fastjsonschema==2.18.0
161 jsonschema==4.18.6
159 jsonschema==4.18.6
162 attrs==22.2.0
160 attrs==22.2.0
163 pyrsistent==0.19.3
161 pyrsistent==0.19.3
164 jupyter_core==5.3.1
162 jupyter_core==5.3.1
165 platformdirs==3.10.0
163 platformdirs==3.10.0
166 traitlets==5.9.0
164 traitlets==5.9.0
167 traitlets==5.9.0
165 traitlets==5.9.0
168 packaging==23.1
169 pandocfilters==1.5.0
166 pandocfilters==1.5.0
170 pygments==2.15.1
167 pygments==2.15.1
171 tinycss2==1.2.1
168 tinycss2==1.2.1
172 webencodings==0.5.1
169 webencodings==0.5.1
173 traitlets==5.9.0
170 traitlets==5.9.0
174 orjson==3.9.13
171 orjson==3.10.3
175 pastescript==3.4.0
172 paste==3.10.1
176 paste==3.7.1
177 six==1.16.0
178 pastedeploy==3.1.0
179 six==1.16.0
180 premailer==3.10.0
173 premailer==3.10.0
181 cachetools==5.3.2
174 cachetools==5.3.3
182 cssselect==1.2.0
175 cssselect==1.2.0
183 cssutils==2.6.0
176 cssutils==2.6.0
184 lxml==4.9.3
177 lxml==4.9.3
185 requests==2.28.2
178 requests==2.28.2
186 certifi==2022.12.7
179 certifi==2022.12.7
187 charset-normalizer==3.1.0
180 charset-normalizer==3.1.0
188 idna==3.4
181 idna==3.4
189 urllib3==1.26.14
182 urllib3==1.26.14
190 psutil==5.9.8
183 psutil==5.9.8
191 psycopg2==2.9.9
184 psycopg2==2.9.9
192 py-bcrypt==0.4
185 py-bcrypt==0.4
193 pycmarkgfm==1.2.0
186 pycmarkgfm==1.2.0
194 cffi==1.16.0
187 cffi==1.16.0
195 pycparser==2.21
188 pycparser==2.21
196 pycryptodome==3.17
189 pycryptodome==3.17
197 pycurl==7.45.2
190 pycurl==7.45.3
198 pymysql==1.0.3
191 pymysql==1.0.3
199 pyotp==2.8.0
192 pyotp==2.8.0
200 pyparsing==3.1.1
193 pyparsing==3.1.1
201 pyramid-debugtoolbar==4.11
194 pyramid-debugtoolbar==4.12.1
202 pygments==2.15.1
195 pygments==2.15.1
203 pyramid==2.0.2
196 pyramid==2.0.2
204 hupper==1.12
197 hupper==1.12
205 plaster==1.1.2
198 plaster==1.1.2
206 plaster-pastedeploy==1.0.1
199 plaster-pastedeploy==1.0.1
207 pastedeploy==3.1.0
200 pastedeploy==3.1.0
208 plaster==1.1.2
201 plaster==1.1.2
209 translationstring==1.4
202 translationstring==1.4
210 venusian==3.0.0
203 venusian==3.0.0
211 webob==1.8.7
204 webob==1.8.7
212 zope.deprecation==5.0.0
205 zope.deprecation==5.0.0
213 zope.interface==6.1.0
206 zope.interface==6.3.0
214 pyramid-mako==1.1.0
207 pyramid-mako==1.1.0
215 mako==1.2.4
208 mako==1.2.4
216 markupsafe==2.1.2
209 markupsafe==2.1.2
217 pyramid==2.0.2
210 pyramid==2.0.2
218 hupper==1.12
211 hupper==1.12
219 plaster==1.1.2
212 plaster==1.1.2
220 plaster-pastedeploy==1.0.1
213 plaster-pastedeploy==1.0.1
221 pastedeploy==3.1.0
214 pastedeploy==3.1.0
222 plaster==1.1.2
215 plaster==1.1.2
223 translationstring==1.4
216 translationstring==1.4
224 venusian==3.0.0
217 venusian==3.0.0
225 webob==1.8.7
218 webob==1.8.7
226 zope.deprecation==5.0.0
219 zope.deprecation==5.0.0
227 zope.interface==6.1.0
220 zope.interface==6.3.0
228 pyramid-mailer==0.15.1
221 pyramid-mailer==0.15.1
229 pyramid==2.0.2
222 pyramid==2.0.2
230 hupper==1.12
223 hupper==1.12
231 plaster==1.1.2
224 plaster==1.1.2
232 plaster-pastedeploy==1.0.1
225 plaster-pastedeploy==1.0.1
233 pastedeploy==3.1.0
226 pastedeploy==3.1.0
234 plaster==1.1.2
227 plaster==1.1.2
235 translationstring==1.4
228 translationstring==1.4
236 venusian==3.0.0
229 venusian==3.0.0
237 webob==1.8.7
230 webob==1.8.7
238 zope.deprecation==5.0.0
231 zope.deprecation==5.0.0
239 zope.interface==6.1.0
232 zope.interface==6.3.0
240 repoze.sendmail==4.4.1
233 repoze.sendmail==4.4.1
241 transaction==3.1.0
234 transaction==3.1.0
242 zope.interface==6.1.0
235 zope.interface==6.3.0
243 zope.interface==6.1.0
236 zope.interface==6.3.0
244 transaction==3.1.0
237 transaction==3.1.0
245 zope.interface==6.1.0
238 zope.interface==6.3.0
246 python-ldap==3.4.3
239 python-ldap==3.4.3
247 pyasn1==0.4.8
240 pyasn1==0.4.8
248 pyasn1-modules==0.2.8
241 pyasn1-modules==0.2.8
249 pyasn1==0.4.8
242 pyasn1==0.4.8
250 python-memcached==1.59
243 python-memcached==1.59
251 six==1.16.0
244 six==1.16.0
252 python-pam==2.0.2
245 python-pam==2.0.2
253 python3-saml==1.15.0
246 python3-saml==1.15.0
254 isodate==0.6.1
247 isodate==0.6.1
255 six==1.16.0
248 six==1.16.0
256 lxml==4.9.3
249 lxml==4.9.3
257 xmlsec==1.3.13
250 xmlsec==1.3.13
258 lxml==4.9.3
251 lxml==4.9.3
259 pyyaml==6.0.1
252 pyyaml==6.0.1
260 redis==5.0.1
253 redis==5.0.4
254 async-timeout==4.0.3
261 regex==2022.10.31
255 regex==2022.10.31
262 routes==2.5.1
256 routes==2.5.1
263 repoze.lru==0.7
257 repoze.lru==0.7
264 six==1.16.0
258 six==1.16.0
265 simplejson==3.19.1
259 s3fs==2024.6.0
260 aiobotocore==2.13.0
261 aiohttp==3.9.5
262 aiosignal==1.3.1
263 frozenlist==1.4.1
264 attrs==22.2.0
265 frozenlist==1.4.1
266 multidict==6.0.5
267 yarl==1.9.4
268 idna==3.4
269 multidict==6.0.5
270 aioitertools==0.11.0
271 botocore==1.34.106
272 jmespath==1.0.1
273 python-dateutil==2.8.2
274 six==1.16.0
275 urllib3==1.26.14
276 wrapt==1.16.0
277 aiohttp==3.9.5
278 aiosignal==1.3.1
279 frozenlist==1.4.1
280 attrs==22.2.0
281 frozenlist==1.4.1
282 multidict==6.0.5
283 yarl==1.9.4
284 idna==3.4
285 multidict==6.0.5
286 fsspec==2024.6.0
287 simplejson==3.19.2
266 sshpubkeys==3.3.1
288 sshpubkeys==3.3.1
267 cryptography==40.0.2
289 cryptography==40.0.2
268 cffi==1.16.0
290 cffi==1.16.0
269 pycparser==2.21
291 pycparser==2.21
270 ecdsa==0.18.0
292 ecdsa==0.18.0
271 six==1.16.0
293 six==1.16.0
272 sqlalchemy==1.4.51
294 sqlalchemy==1.4.52
273 greenlet==3.0.3
295 greenlet==3.0.3
274 typing_extensions==4.9.0
296 typing_extensions==4.9.0
275 supervisor==4.2.5
297 supervisor==4.2.5
276 tzlocal==4.3
298 tzlocal==4.3
277 pytz-deprecation-shim==0.1.0.post0
299 pytz-deprecation-shim==0.1.0.post0
278 tzdata==2023.4
300 tzdata==2024.1
301 tempita==0.5.2
279 unidecode==1.3.6
302 unidecode==1.3.6
280 urlobject==2.4.3
303 urlobject==2.4.3
281 waitress==3.0.0
304 waitress==3.0.0
282 weberror==0.13.1
305 webhelpers2==2.1
283 paste==3.7.1
284 six==1.16.0
285 pygments==2.15.1
286 tempita==0.5.2
287 webob==1.8.7
288 webhelpers2==2.0
289 markupsafe==2.1.2
306 markupsafe==2.1.2
290 six==1.16.0
307 six==1.16.0
291 whoosh==2.7.4
308 whoosh==2.7.4
292 zope.cachedescriptors==5.0.0
309 zope.cachedescriptors==5.0.0
310 qrcode==7.4.2
293
311
294 ## uncomment to add the debug libraries
312 ## uncomment to add the debug libraries
295 #-r requirements_debug.txt
313 #-r requirements_debug.txt
@@ -1,46 +1,48 b''
1 # test related requirements
1 # test related requirements
2
2 mock==5.1.0
3 cov-core==1.15.0
3 pytest-cov==4.1.0
4 coverage==7.2.3
4 coverage==7.4.3
5 mock==5.0.2
5 pytest==8.1.1
6 py==1.11.0
7 pytest-cov==4.0.0
8 coverage==7.2.3
9 pytest==7.3.1
10 attrs==22.2.0
11 iniconfig==2.0.0
6 iniconfig==2.0.0
12 packaging==23.1
7 packaging==24.0
13 pluggy==1.0.0
8 pluggy==1.4.0
14 pytest-rerunfailures==12.0
9 pytest-env==1.1.3
10 pytest==8.1.1
11 iniconfig==2.0.0
12 packaging==24.0
13 pluggy==1.4.0
15 pytest-profiling==1.7.0
14 pytest-profiling==1.7.0
16 gprof2dot==2022.7.29
15 gprof2dot==2022.7.29
17 pytest==7.3.1
16 pytest==8.1.1
18 attrs==22.2.0
19 iniconfig==2.0.0
17 iniconfig==2.0.0
20 packaging==23.1
18 packaging==24.0
21 pluggy==1.0.0
19 pluggy==1.4.0
22 six==1.16.0
20 six==1.16.0
23 pytest-runner==6.0.0
21 pytest-rerunfailures==13.0
24 pytest-sugar==0.9.7
22 packaging==24.0
25 packaging==23.1
23 pytest==8.1.1
26 pytest==7.3.1
27 attrs==22.2.0
28 iniconfig==2.0.0
24 iniconfig==2.0.0
29 packaging==23.1
25 packaging==24.0
30 pluggy==1.0.0
26 pluggy==1.4.0
31 termcolor==2.3.0
27 pytest-runner==6.0.1
32 pytest-timeout==2.1.0
28 pytest-sugar==1.0.0
33 pytest==7.3.1
29 packaging==24.0
34 attrs==22.2.0
30 pytest==8.1.1
35 iniconfig==2.0.0
31 iniconfig==2.0.0
36 packaging==23.1
32 packaging==24.0
37 pluggy==1.0.0
33 pluggy==1.4.0
34 termcolor==2.4.0
35 pytest-timeout==2.3.1
36 pytest==8.1.1
37 iniconfig==2.0.0
38 packaging==24.0
39 pluggy==1.4.0
38 webtest==3.0.0
40 webtest==3.0.0
39 beautifulsoup4==4.11.2
41 beautifulsoup4==4.12.3
40 soupsieve==2.4
42 soupsieve==2.5
41 waitress==3.0.0
43 waitress==3.0.0
42 webob==1.8.7
44 webob==1.8.7
43
45
44 # RhodeCode test-data
46 # RhodeCode test-data
45 rc_testdata @ https://code.rhodecode.com/upstream/rc-testdata-dist/raw/77378e9097f700b4c1b9391b56199fe63566b5c9/rc_testdata-0.11.0.tar.gz#egg=rc_testdata
47 rc_testdata @ https://code.rhodecode.com/upstream/rc-testdata-dist/raw/77378e9097f700b4c1b9391b56199fe63566b5c9/rc_testdata-0.11.0.tar.gz#egg=rc_testdata
46 rc_testdata==0.11.0
48 rc_testdata==0.11.0
@@ -1,1 +1,1 b''
1 5.0.3 No newline at end of file
1 5.1.0 No newline at end of file
@@ -1,91 +1,91 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import os
19 import os
20 import datetime
20 import datetime
21 import collections
21 import collections
22 import logging
22 import logging
23
23
24
24
25 now = datetime.datetime.now()
25 now = datetime.datetime.now()
26 now = now.strftime("%Y-%m-%d %H:%M:%S") + '.' + f"{int(now.microsecond/1000):03d}"
26 now = now.strftime("%Y-%m-%d %H:%M:%S") + '.' + f"{int(now.microsecond/1000):03d}"
27
27
28 log = logging.getLogger(__name__)
28 log = logging.getLogger(__name__)
29 log.debug(f'{now} Starting RhodeCode imports...')
29 log.debug(f'{now} Starting RhodeCode imports...')
30
30
31
31
32 VERSION = tuple(open(os.path.join(
32 VERSION = tuple(open(os.path.join(
33 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33 os.path.dirname(__file__), 'VERSION')).read().split('.'))
34
34
35 BACKENDS = collections.OrderedDict()
35 BACKENDS = collections.OrderedDict()
36
36
37 BACKENDS['hg'] = 'Mercurial repository'
37 BACKENDS['hg'] = 'Mercurial repository'
38 BACKENDS['git'] = 'Git repository'
38 BACKENDS['git'] = 'Git repository'
39 BACKENDS['svn'] = 'Subversion repository'
39 BACKENDS['svn'] = 'Subversion repository'
40
40
41
41
42 CELERY_ENABLED = False
42 CELERY_ENABLED = False
43 CELERY_EAGER = False
43 CELERY_EAGER = False
44
44
45 # link to config for pyramid
45 # link to config for pyramid
46 CONFIG = {}
46 CONFIG = {}
47
47
48
48
49 class ConfigGet:
49 class ConfigGet:
50 NotGiven = object()
50 NotGiven = object()
51
51
52 def _get_val_or_missing(self, key, missing):
52 def _get_val_or_missing(self, key, missing):
53 if key not in CONFIG:
53 if key not in CONFIG:
54 if missing == self.NotGiven:
54 if missing == self.NotGiven:
55 return missing
55 return missing
56 # we don't get key, we don't get missing value, return nothing similar as config.get(key)
56 # we don't get key, we don't get missing value, return nothing similar as config.get(key)
57 return None
57 return None
58 else:
58 else:
59 val = CONFIG[key]
59 val = CONFIG[key]
60 return val
60 return val
61
61
62 def get_str(self, key, missing=NotGiven):
62 def get_str(self, key, missing=NotGiven):
63 from rhodecode.lib.str_utils import safe_str
63 from rhodecode.lib.str_utils import safe_str
64 val = self._get_val_or_missing(key, missing)
64 val = self._get_val_or_missing(key, missing)
65 return safe_str(val)
65 return safe_str(val)
66
66
67 def get_int(self, key, missing=NotGiven):
67 def get_int(self, key, missing=NotGiven):
68 from rhodecode.lib.str_utils import safe_int
68 from rhodecode.lib.str_utils import safe_int
69 val = self._get_val_or_missing(key, missing)
69 val = self._get_val_or_missing(key, missing)
70 return safe_int(val)
70 return safe_int(val)
71
71
72 def get_bool(self, key, missing=NotGiven):
72 def get_bool(self, key, missing=NotGiven):
73 from rhodecode.lib.type_utils import str2bool
73 from rhodecode.lib.type_utils import str2bool
74 val = self._get_val_or_missing(key, missing)
74 val = self._get_val_or_missing(key, missing)
75 return str2bool(val)
75 return str2bool(val)
76
76
77 # Populated with the settings dictionary from application init in
77 # Populated with the settings dictionary from application init in
78 # rhodecode.conf.environment.load_pyramid_environment
78 # rhodecode.conf.environment.load_pyramid_environment
79 PYRAMID_SETTINGS = {}
79 PYRAMID_SETTINGS = {}
80
80
81 # Linked module for extensions
81 # Linked module for extensions
82 EXTENSIONS = {}
82 EXTENSIONS = {}
83
83
84 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
84 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
85 __dbversion__ = 114 # defines current db version for migrations
85 __dbversion__ = 115 # defines current db version for migrations
86 __license__ = 'AGPLv3, and Commercial License'
86 __license__ = 'AGPLv3, and Commercial License'
87 __author__ = 'RhodeCode GmbH'
87 __author__ = 'RhodeCode GmbH'
88 __url__ = 'https://code.rhodecode.com'
88 __url__ = 'https://code.rhodecode.com'
89
89
90 is_test = False
90 is_test = os.getenv('RC_TEST', '0') == '1'
91 disable_error_handler = False
91 disable_error_handler = False
@@ -1,573 +1,581 b''
1 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import itertools
19 import itertools
20 import logging
20 import logging
21 import sys
21 import sys
22 import fnmatch
22 import fnmatch
23
23
24 import decorator
24 import decorator
25 import typing
26 import venusian
25 import venusian
27 from collections import OrderedDict
26 from collections import OrderedDict
28
27
29 from pyramid.exceptions import ConfigurationError
28 from pyramid.exceptions import ConfigurationError
30 from pyramid.renderers import render
29 from pyramid.renderers import render
31 from pyramid.response import Response
30 from pyramid.response import Response
32 from pyramid.httpexceptions import HTTPNotFound
31 from pyramid.httpexceptions import HTTPNotFound
33
32
34 from rhodecode.api.exc import (
33 from rhodecode.api.exc import (
35 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
34 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
36 from rhodecode.apps._base import TemplateArgs
35 from rhodecode.apps._base import TemplateArgs
37 from rhodecode.lib.auth import AuthUser
36 from rhodecode.lib.auth import AuthUser
38 from rhodecode.lib.base import get_ip_addr, attach_context_attributes
37 from rhodecode.lib.base import get_ip_addr, attach_context_attributes
39 from rhodecode.lib.exc_tracking import store_exception
38 from rhodecode.lib.exc_tracking import store_exception
40 from rhodecode.lib import ext_json
39 from rhodecode.lib import ext_json
41 from rhodecode.lib.utils2 import safe_str
40 from rhodecode.lib.utils2 import safe_str
42 from rhodecode.lib.plugins.utils import get_plugin_settings
41 from rhodecode.lib.plugins.utils import get_plugin_settings
43 from rhodecode.model.db import User, UserApiKeys
42 from rhodecode.model.db import User, UserApiKeys
44
43
45 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
46
45
47 DEFAULT_RENDERER = 'jsonrpc_renderer'
46 DEFAULT_RENDERER = 'jsonrpc_renderer'
48 DEFAULT_URL = '/_admin/apiv2'
47 DEFAULT_URL = '/_admin/api'
48 SERVICE_API_IDENTIFIER = 'service_'
49
49
50
50
51 def find_methods(jsonrpc_methods, pattern):
51 def find_methods(jsonrpc_methods, pattern):
52 matches = OrderedDict()
52 matches = OrderedDict()
53 if not isinstance(pattern, (list, tuple)):
53 if not isinstance(pattern, (list, tuple)):
54 pattern = [pattern]
54 pattern = [pattern]
55
55
56 for single_pattern in pattern:
56 for single_pattern in pattern:
57 for method_name, method in jsonrpc_methods.items():
57 for method_name, method in filter(
58 lambda x: not x[0].startswith(SERVICE_API_IDENTIFIER), jsonrpc_methods.items()
59 ):
58 if fnmatch.fnmatch(method_name, single_pattern):
60 if fnmatch.fnmatch(method_name, single_pattern):
59 matches[method_name] = method
61 matches[method_name] = method
60 return matches
62 return matches
61
63
62
64
63 class ExtJsonRenderer(object):
65 class ExtJsonRenderer(object):
64 """
66 """
65 Custom renderer that makes use of our ext_json lib
67 Custom renderer that makes use of our ext_json lib
66
68
67 """
69 """
68
70
69 def __init__(self):
71 def __init__(self):
70 self.serializer = ext_json.formatted_json
72 self.serializer = ext_json.formatted_json
71
73
72 def __call__(self, info):
74 def __call__(self, info):
73 """ Returns a plain JSON-encoded string with content-type
75 """ Returns a plain JSON-encoded string with content-type
74 ``application/json``. The content-type may be overridden by
76 ``application/json``. The content-type may be overridden by
75 setting ``request.response.content_type``."""
77 setting ``request.response.content_type``."""
76
78
77 def _render(value, system):
79 def _render(value, system):
78 request = system.get('request')
80 request = system.get('request')
79 if request is not None:
81 if request is not None:
80 response = request.response
82 response = request.response
81 ct = response.content_type
83 ct = response.content_type
82 if ct == response.default_content_type:
84 if ct == response.default_content_type:
83 response.content_type = 'application/json'
85 response.content_type = 'application/json'
84
86
85 return self.serializer(value)
87 return self.serializer(value)
86
88
87 return _render
89 return _render
88
90
89
91
90 def jsonrpc_response(request, result):
92 def jsonrpc_response(request, result):
91 rpc_id = getattr(request, 'rpc_id', None)
93 rpc_id = getattr(request, 'rpc_id', None)
92
94
93 ret_value = ''
95 ret_value = ''
94 if rpc_id:
96 if rpc_id:
95 ret_value = {'id': rpc_id, 'result': result, 'error': None}
97 ret_value = {'id': rpc_id, 'result': result, 'error': None}
96
98
97 # fetch deprecation warnings, and store it inside results
99 # fetch deprecation warnings, and store it inside results
98 deprecation = getattr(request, 'rpc_deprecation', None)
100 deprecation = getattr(request, 'rpc_deprecation', None)
99 if deprecation:
101 if deprecation:
100 ret_value['DEPRECATION_WARNING'] = deprecation
102 ret_value['DEPRECATION_WARNING'] = deprecation
101
103
102 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
104 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
103 content_type = 'application/json'
105 content_type = 'application/json'
104 content_type_header = 'Content-Type'
106 content_type_header = 'Content-Type'
105 headers = {
107 headers = {
106 content_type_header: content_type
108 content_type_header: content_type
107 }
109 }
108 return Response(
110 return Response(
109 body=raw_body,
111 body=raw_body,
110 content_type=content_type,
112 content_type=content_type,
111 headerlist=[(k, v) for k, v in headers.items()]
113 headerlist=[(k, v) for k, v in headers.items()]
112 )
114 )
113
115
114
116
115 def jsonrpc_error(request, message, retid=None, code: int | None = None, headers: dict | None = None):
117 def jsonrpc_error(request, message, retid=None, code: int | None = None, headers: dict | None = None):
116 """
118 """
117 Generate a Response object with a JSON-RPC error body
119 Generate a Response object with a JSON-RPC error body
118 """
120 """
119 headers = headers or {}
121 headers = headers or {}
120 content_type = 'application/json'
122 content_type = 'application/json'
121 content_type_header = 'Content-Type'
123 content_type_header = 'Content-Type'
122 if content_type_header not in headers:
124 if content_type_header not in headers:
123 headers[content_type_header] = content_type
125 headers[content_type_header] = content_type
124
126
125 err_dict = {'id': retid, 'result': None, 'error': message}
127 err_dict = {'id': retid, 'result': None, 'error': message}
126 raw_body = render(DEFAULT_RENDERER, err_dict, request=request)
128 raw_body = render(DEFAULT_RENDERER, err_dict, request=request)
127
129
128 return Response(
130 return Response(
129 body=raw_body,
131 body=raw_body,
130 status=code,
132 status=code,
131 content_type=content_type,
133 content_type=content_type,
132 headerlist=[(k, v) for k, v in headers.items()]
134 headerlist=[(k, v) for k, v in headers.items()]
133 )
135 )
134
136
135
137
136 def exception_view(exc, request):
138 def exception_view(exc, request):
137 rpc_id = getattr(request, 'rpc_id', None)
139 rpc_id = getattr(request, 'rpc_id', None)
138
140
139 if isinstance(exc, JSONRPCError):
141 if isinstance(exc, JSONRPCError):
140 fault_message = safe_str(exc)
142 fault_message = safe_str(exc)
141 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
143 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
142 elif isinstance(exc, JSONRPCValidationError):
144 elif isinstance(exc, JSONRPCValidationError):
143 colander_exc = exc.colander_exception
145 colander_exc = exc.colander_exception
144 # TODO(marcink): think maybe of nicer way to serialize errors ?
146 # TODO(marcink): think maybe of nicer way to serialize errors ?
145 fault_message = colander_exc.asdict()
147 fault_message = colander_exc.asdict()
146 log.debug('json-rpc colander error rpc_id:%s "%s"', rpc_id, fault_message)
148 log.debug('json-rpc colander error rpc_id:%s "%s"', rpc_id, fault_message)
147 elif isinstance(exc, JSONRPCForbidden):
149 elif isinstance(exc, JSONRPCForbidden):
148 fault_message = 'Access was denied to this resource.'
150 fault_message = 'Access was denied to this resource.'
149 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
151 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
150 elif isinstance(exc, HTTPNotFound):
152 elif isinstance(exc, HTTPNotFound):
151 method = request.rpc_method
153 method = request.rpc_method
152 log.debug('json-rpc method `%s` not found in list of '
154 log.debug('json-rpc method `%s` not found in list of '
153 'api calls: %s, rpc_id:%s',
155 'api calls: %s, rpc_id:%s',
154 method, list(request.registry.jsonrpc_methods.keys()), rpc_id)
156 method, list(request.registry.jsonrpc_methods.keys()), rpc_id)
155
157
156 similar = 'none'
158 similar = 'none'
157 try:
159 try:
158 similar_paterns = [f'*{x}*' for x in method.split('_')]
160 similar_paterns = [f'*{x}*' for x in method.split('_')]
159 similar_found = find_methods(
161 similar_found = find_methods(
160 request.registry.jsonrpc_methods, similar_paterns)
162 request.registry.jsonrpc_methods, similar_paterns)
161 similar = ', '.join(similar_found.keys()) or similar
163 similar = ', '.join(similar_found.keys()) or similar
162 except Exception:
164 except Exception:
163 # make the whole above block safe
165 # make the whole above block safe
164 pass
166 pass
165
167
166 fault_message = f"No such method: {method}. Similar methods: {similar}"
168 fault_message = f"No such method: {method}. Similar methods: {similar}"
167 else:
169 else:
168 fault_message = 'undefined error'
170 fault_message = 'undefined error'
169 exc_info = exc.exc_info()
171 exc_info = exc.exc_info()
170 store_exception(id(exc_info), exc_info, prefix='rhodecode-api')
172 store_exception(id(exc_info), exc_info, prefix='rhodecode-api')
171
173
172 statsd = request.registry.statsd
174 statsd = request.registry.statsd
173 if statsd:
175 if statsd:
174 exc_type = f"{exc.__class__.__module__}.{exc.__class__.__name__}"
176 exc_type = f"{exc.__class__.__module__}.{exc.__class__.__name__}"
175 statsd.incr('rhodecode_exception_total',
177 statsd.incr('rhodecode_exception_total',
176 tags=["exc_source:api", f"type:{exc_type}"])
178 tags=["exc_source:api", f"type:{exc_type}"])
177
179
178 return jsonrpc_error(request, fault_message, rpc_id)
180 return jsonrpc_error(request, fault_message, rpc_id)
179
181
180
182
181 def request_view(request):
183 def request_view(request):
182 """
184 """
183 Main request handling method. It handles all logic to call a specific
185 Main request handling method. It handles all logic to call a specific
184 exposed method
186 exposed method
185 """
187 """
186 # cython compatible inspect
188 # cython compatible inspect
187 from rhodecode.config.patches import inspect_getargspec
189 from rhodecode.config.patches import inspect_getargspec
188 inspect = inspect_getargspec()
190 inspect = inspect_getargspec()
189
191
190 # check if we can find this session using api_key, get_by_auth_token
192 # check if we can find this session using api_key, get_by_auth_token
191 # search not expired tokens only
193 # search not expired tokens only
192 try:
194 try:
193 api_user = User.get_by_auth_token(request.rpc_api_key)
195 if not request.rpc_method.startswith(SERVICE_API_IDENTIFIER):
196 api_user = User.get_by_auth_token(request.rpc_api_key)
194
197
195 if api_user is None:
198 if api_user is None:
196 return jsonrpc_error(
199 return jsonrpc_error(
197 request, retid=request.rpc_id, message='Invalid API KEY')
200 request, retid=request.rpc_id, message='Invalid API KEY')
198
201
199 if not api_user.active:
202 if not api_user.active:
200 return jsonrpc_error(
203 return jsonrpc_error(
201 request, retid=request.rpc_id,
204 request, retid=request.rpc_id,
202 message='Request from this user not allowed')
205 message='Request from this user not allowed')
203
206
204 # check if we are allowed to use this IP
207 # check if we are allowed to use this IP
205 auth_u = AuthUser(
208 auth_u = AuthUser(
206 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
209 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
207 if not auth_u.ip_allowed:
210 if not auth_u.ip_allowed:
208 return jsonrpc_error(
211 return jsonrpc_error(
209 request, retid=request.rpc_id,
212 request, retid=request.rpc_id,
210 message='Request from IP:{} not allowed'.format(
213 message='Request from IP:{} not allowed'.format(
211 request.rpc_ip_addr))
214 request.rpc_ip_addr))
212 else:
215 else:
213 log.info('Access for IP:%s allowed', request.rpc_ip_addr)
216 log.info('Access for IP:%s allowed', request.rpc_ip_addr)
217
218 # register our auth-user
219 request.rpc_user = auth_u
220 request.environ['rc_auth_user_id'] = str(auth_u.user_id)
214
221
215 # register our auth-user
222 # now check if token is valid for API
216 request.rpc_user = auth_u
223 auth_token = request.rpc_api_key
217 request.environ['rc_auth_user_id'] = str(auth_u.user_id)
224 token_match = api_user.authenticate_by_token(
225 auth_token, roles=[UserApiKeys.ROLE_API])
226 invalid_token = not token_match
218
227
219 # now check if token is valid for API
228 log.debug('Checking if API KEY is valid with proper role')
220 auth_token = request.rpc_api_key
229 if invalid_token:
221 token_match = api_user.authenticate_by_token(
230 return jsonrpc_error(
222 auth_token, roles=[UserApiKeys.ROLE_API])
231 request, retid=request.rpc_id,
223 invalid_token = not token_match
232 message='API KEY invalid or, has bad role for an API call')
224
233 else:
225 log.debug('Checking if API KEY is valid with proper role')
234 auth_u = 'service'
226 if invalid_token:
235 if request.rpc_api_key != request.registry.settings['app.service_api.token']:
227 return jsonrpc_error(
236 raise Exception("Provided service secret is not recognized!")
228 request, retid=request.rpc_id,
229 message='API KEY invalid or, has bad role for an API call')
230
237
231 except Exception:
238 except Exception:
232 log.exception('Error on API AUTH')
239 log.exception('Error on API AUTH')
233 return jsonrpc_error(
240 return jsonrpc_error(
234 request, retid=request.rpc_id, message='Invalid API KEY')
241 request, retid=request.rpc_id, message='Invalid API KEY')
235
242
236 method = request.rpc_method
243 method = request.rpc_method
237 func = request.registry.jsonrpc_methods[method]
244 func = request.registry.jsonrpc_methods[method]
238
245
239 # now that we have a method, add request._req_params to
246 # now that we have a method, add request._req_params to
240 # self.kargs and dispatch control to WGIController
247 # self.kargs and dispatch control to WGIController
241
248
242 argspec = inspect.getargspec(func)
249 argspec = inspect.getargspec(func)
243 arglist = argspec[0]
250 arglist = argspec[0]
244 defs = argspec[3] or []
251 defs = argspec[3] or []
245 defaults = [type(a) for a in defs]
252 defaults = [type(a) for a in defs]
246 default_empty = type(NotImplemented)
253 default_empty = type(NotImplemented)
247
254
248 # kw arguments required by this method
255 # kw arguments required by this method
249 func_kwargs = dict(itertools.zip_longest(
256 func_kwargs = dict(itertools.zip_longest(
250 reversed(arglist), reversed(defaults), fillvalue=default_empty))
257 reversed(arglist), reversed(defaults), fillvalue=default_empty))
251
258
252 # This attribute will need to be first param of a method that uses
259 # This attribute will need to be first param of a method that uses
253 # api_key, which is translated to instance of user at that name
260 # api_key, which is translated to instance of user at that name
254 user_var = 'apiuser'
261 user_var = 'apiuser'
255 request_var = 'request'
262 request_var = 'request'
256
263
257 for arg in [user_var, request_var]:
264 for arg in [user_var, request_var]:
258 if arg not in arglist:
265 if arg not in arglist:
259 return jsonrpc_error(
266 return jsonrpc_error(
260 request,
267 request,
261 retid=request.rpc_id,
268 retid=request.rpc_id,
262 message='This method [%s] does not support '
269 message='This method [%s] does not support '
263 'required parameter `%s`' % (func.__name__, arg))
270 'required parameter `%s`' % (func.__name__, arg))
264
271
265 # get our arglist and check if we provided them as args
272 # get our arglist and check if we provided them as args
266 for arg, default in func_kwargs.items():
273 for arg, default in func_kwargs.items():
267 if arg in [user_var, request_var]:
274 if arg in [user_var, request_var]:
268 # user_var and request_var are pre-hardcoded parameters and we
275 # user_var and request_var are pre-hardcoded parameters and we
269 # don't need to do any translation
276 # don't need to do any translation
270 continue
277 continue
271
278
272 # skip the required param check if it's default value is
279 # skip the required param check if it's default value is
273 # NotImplementedType (default_empty)
280 # NotImplementedType (default_empty)
274 if default == default_empty and arg not in request.rpc_params:
281 if default == default_empty and arg not in request.rpc_params:
275 return jsonrpc_error(
282 return jsonrpc_error(
276 request,
283 request,
277 retid=request.rpc_id,
284 retid=request.rpc_id,
278 message=('Missing non optional `%s` arg in JSON DATA' % arg)
285 message=('Missing non optional `%s` arg in JSON DATA' % arg)
279 )
286 )
280
287
281 # sanitize extra passed arguments
288 # sanitize extra passed arguments
282 for k in list(request.rpc_params.keys()):
289 for k in list(request.rpc_params.keys()):
283 if k not in func_kwargs:
290 if k not in func_kwargs:
284 del request.rpc_params[k]
291 del request.rpc_params[k]
285
292
286 call_params = request.rpc_params
293 call_params = request.rpc_params
287 call_params.update({
294 call_params.update({
288 'request': request,
295 'request': request,
289 'apiuser': auth_u
296 'apiuser': auth_u
290 })
297 })
291
298
292 # register some common functions for usage
299 # register some common functions for usage
293 attach_context_attributes(TemplateArgs(), request, request.rpc_user.user_id)
300 rpc_user = request.rpc_user.user_id if hasattr(request, 'rpc_user') else None
301 attach_context_attributes(TemplateArgs(), request, rpc_user)
294
302
295 statsd = request.registry.statsd
303 statsd = request.registry.statsd
296
304
297 try:
305 try:
298 ret_value = func(**call_params)
306 ret_value = func(**call_params)
299 resp = jsonrpc_response(request, ret_value)
307 resp = jsonrpc_response(request, ret_value)
300 if statsd:
308 if statsd:
301 statsd.incr('rhodecode_api_call_success_total')
309 statsd.incr('rhodecode_api_call_success_total')
302 return resp
310 return resp
303 except JSONRPCBaseError:
311 except JSONRPCBaseError:
304 raise
312 raise
305 except Exception:
313 except Exception:
306 log.exception('Unhandled exception occurred on api call: %s', func)
314 log.exception('Unhandled exception occurred on api call: %s', func)
307 exc_info = sys.exc_info()
315 exc_info = sys.exc_info()
308 exc_id, exc_type_name = store_exception(
316 exc_id, exc_type_name = store_exception(
309 id(exc_info), exc_info, prefix='rhodecode-api')
317 id(exc_info), exc_info, prefix='rhodecode-api')
310 error_headers = {
318 error_headers = {
311 'RhodeCode-Exception-Id': str(exc_id),
319 'RhodeCode-Exception-Id': str(exc_id),
312 'RhodeCode-Exception-Type': str(exc_type_name)
320 'RhodeCode-Exception-Type': str(exc_type_name)
313 }
321 }
314 err_resp = jsonrpc_error(
322 err_resp = jsonrpc_error(
315 request, retid=request.rpc_id, message='Internal server error',
323 request, retid=request.rpc_id, message='Internal server error',
316 headers=error_headers)
324 headers=error_headers)
317 if statsd:
325 if statsd:
318 statsd.incr('rhodecode_api_call_fail_total')
326 statsd.incr('rhodecode_api_call_fail_total')
319 return err_resp
327 return err_resp
320
328
321
329
322 def setup_request(request):
330 def setup_request(request):
323 """
331 """
324 Parse a JSON-RPC request body. It's used inside the predicates method
332 Parse a JSON-RPC request body. It's used inside the predicates method
325 to validate and bootstrap requests for usage in rpc calls.
333 to validate and bootstrap requests for usage in rpc calls.
326
334
327 We need to raise JSONRPCError here if we want to return some errors back to
335 We need to raise JSONRPCError here if we want to return some errors back to
328 user.
336 user.
329 """
337 """
330
338
331 log.debug('Executing setup request: %r', request)
339 log.debug('Executing setup request: %r', request)
332 request.rpc_ip_addr = get_ip_addr(request.environ)
340 request.rpc_ip_addr = get_ip_addr(request.environ)
333 # TODO(marcink): deprecate GET at some point
341 # TODO(marcink): deprecate GET at some point
334 if request.method not in ['POST', 'GET']:
342 if request.method not in ['POST', 'GET']:
335 log.debug('unsupported request method "%s"', request.method)
343 log.debug('unsupported request method "%s"', request.method)
336 raise JSONRPCError(
344 raise JSONRPCError(
337 'unsupported request method "%s". Please use POST' % request.method)
345 'unsupported request method "%s". Please use POST' % request.method)
338
346
339 if 'CONTENT_LENGTH' not in request.environ:
347 if 'CONTENT_LENGTH' not in request.environ:
340 log.debug("No Content-Length")
348 log.debug("No Content-Length")
341 raise JSONRPCError("Empty body, No Content-Length in request")
349 raise JSONRPCError("Empty body, No Content-Length in request")
342
350
343 else:
351 else:
344 length = request.environ['CONTENT_LENGTH']
352 length = request.environ['CONTENT_LENGTH']
345 log.debug('Content-Length: %s', length)
353 log.debug('Content-Length: %s', length)
346
354
347 if length == 0:
355 if length == 0:
348 log.debug("Content-Length is 0")
356 log.debug("Content-Length is 0")
349 raise JSONRPCError("Content-Length is 0")
357 raise JSONRPCError("Content-Length is 0")
350
358
351 raw_body = request.body
359 raw_body = request.body
352 log.debug("Loading JSON body now")
360 log.debug("Loading JSON body now")
353 try:
361 try:
354 json_body = ext_json.json.loads(raw_body)
362 json_body = ext_json.json.loads(raw_body)
355 except ValueError as e:
363 except ValueError as e:
356 # catch JSON errors Here
364 # catch JSON errors Here
357 raise JSONRPCError(f"JSON parse error ERR:{e} RAW:{raw_body!r}")
365 raise JSONRPCError(f"JSON parse error ERR:{e} RAW:{raw_body!r}")
358
366
359 request.rpc_id = json_body.get('id')
367 request.rpc_id = json_body.get('id')
360 request.rpc_method = json_body.get('method')
368 request.rpc_method = json_body.get('method')
361
369
362 # check required base parameters
370 # check required base parameters
363 try:
371 try:
364 api_key = json_body.get('api_key')
372 api_key = json_body.get('api_key')
365 if not api_key:
373 if not api_key:
366 api_key = json_body.get('auth_token')
374 api_key = json_body.get('auth_token')
367
375
368 if not api_key:
376 if not api_key:
369 raise KeyError('api_key or auth_token')
377 raise KeyError('api_key or auth_token')
370
378
371 # TODO(marcink): support passing in token in request header
379 # TODO(marcink): support passing in token in request header
372
380
373 request.rpc_api_key = api_key
381 request.rpc_api_key = api_key
374 request.rpc_id = json_body['id']
382 request.rpc_id = json_body['id']
375 request.rpc_method = json_body['method']
383 request.rpc_method = json_body['method']
376 request.rpc_params = json_body['args'] \
384 request.rpc_params = json_body['args'] \
377 if isinstance(json_body['args'], dict) else {}
385 if isinstance(json_body['args'], dict) else {}
378
386
379 log.debug('method: %s, params: %.10240r', request.rpc_method, request.rpc_params)
387 log.debug('method: %s, params: %.10240r', request.rpc_method, request.rpc_params)
380 except KeyError as e:
388 except KeyError as e:
381 raise JSONRPCError(f'Incorrect JSON data. Missing {e}')
389 raise JSONRPCError(f'Incorrect JSON data. Missing {e}')
382
390
383 log.debug('setup complete, now handling method:%s rpcid:%s',
391 log.debug('setup complete, now handling method:%s rpcid:%s',
384 request.rpc_method, request.rpc_id, )
392 request.rpc_method, request.rpc_id, )
385
393
386
394
387 class RoutePredicate(object):
395 class RoutePredicate(object):
388 def __init__(self, val, config):
396 def __init__(self, val, config):
389 self.val = val
397 self.val = val
390
398
391 def text(self):
399 def text(self):
392 return f'jsonrpc route = {self.val}'
400 return f'jsonrpc route = {self.val}'
393
401
394 phash = text
402 phash = text
395
403
396 def __call__(self, info, request):
404 def __call__(self, info, request):
397 if self.val:
405 if self.val:
398 # potentially setup and bootstrap our call
406 # potentially setup and bootstrap our call
399 setup_request(request)
407 setup_request(request)
400
408
401 # Always return True so that even if it isn't a valid RPC it
409 # Always return True so that even if it isn't a valid RPC it
402 # will fall through to the underlaying handlers like notfound_view
410 # will fall through to the underlaying handlers like notfound_view
403 return True
411 return True
404
412
405
413
406 class NotFoundPredicate(object):
414 class NotFoundPredicate(object):
407 def __init__(self, val, config):
415 def __init__(self, val, config):
408 self.val = val
416 self.val = val
409 self.methods = config.registry.jsonrpc_methods
417 self.methods = config.registry.jsonrpc_methods
410
418
411 def text(self):
419 def text(self):
412 return f'jsonrpc method not found = {self.val}'
420 return f'jsonrpc method not found = {self.val}'
413
421
414 phash = text
422 phash = text
415
423
416 def __call__(self, info, request):
424 def __call__(self, info, request):
417 return hasattr(request, 'rpc_method')
425 return hasattr(request, 'rpc_method')
418
426
419
427
420 class MethodPredicate(object):
428 class MethodPredicate(object):
421 def __init__(self, val, config):
429 def __init__(self, val, config):
422 self.method = val
430 self.method = val
423
431
424 def text(self):
432 def text(self):
425 return f'jsonrpc method = {self.method}'
433 return f'jsonrpc method = {self.method}'
426
434
427 phash = text
435 phash = text
428
436
429 def __call__(self, context, request):
437 def __call__(self, context, request):
430 # we need to explicitly return False here, so pyramid doesn't try to
438 # we need to explicitly return False here, so pyramid doesn't try to
431 # execute our view directly. We need our main handler to execute things
439 # execute our view directly. We need our main handler to execute things
432 return getattr(request, 'rpc_method') == self.method
440 return getattr(request, 'rpc_method') == self.method
433
441
434
442
435 def add_jsonrpc_method(config, view, **kwargs):
443 def add_jsonrpc_method(config, view, **kwargs):
436 # pop the method name
444 # pop the method name
437 method = kwargs.pop('method', None)
445 method = kwargs.pop('method', None)
438
446
439 if method is None:
447 if method is None:
440 raise ConfigurationError(
448 raise ConfigurationError(
441 'Cannot register a JSON-RPC method without specifying the "method"')
449 'Cannot register a JSON-RPC method without specifying the "method"')
442
450
443 # we define custom predicate, to enable to detect conflicting methods,
451 # we define custom predicate, to enable to detect conflicting methods,
444 # those predicates are kind of "translation" from the decorator variables
452 # those predicates are kind of "translation" from the decorator variables
445 # to internal predicates names
453 # to internal predicates names
446
454
447 kwargs['jsonrpc_method'] = method
455 kwargs['jsonrpc_method'] = method
448
456
449 # register our view into global view store for validation
457 # register our view into global view store for validation
450 config.registry.jsonrpc_methods[method] = view
458 config.registry.jsonrpc_methods[method] = view
451
459
452 # we're using our main request_view handler, here, so each method
460 # we're using our main request_view handler, here, so each method
453 # has a unified handler for itself
461 # has a unified handler for itself
454 config.add_view(request_view, route_name='apiv2', **kwargs)
462 config.add_view(request_view, route_name='apiv2', **kwargs)
455
463
456
464
457 class jsonrpc_method(object):
465 class jsonrpc_method(object):
458 """
466 """
459 decorator that works similar to @add_view_config decorator,
467 decorator that works similar to @add_view_config decorator,
460 but tailored for our JSON RPC
468 but tailored for our JSON RPC
461 """
469 """
462
470
463 venusian = venusian # for testing injection
471 venusian = venusian # for testing injection
464
472
465 def __init__(self, method=None, **kwargs):
473 def __init__(self, method=None, **kwargs):
466 self.method = method
474 self.method = method
467 self.kwargs = kwargs
475 self.kwargs = kwargs
468
476
469 def __call__(self, wrapped):
477 def __call__(self, wrapped):
470 kwargs = self.kwargs.copy()
478 kwargs = self.kwargs.copy()
471 kwargs['method'] = self.method or wrapped.__name__
479 kwargs['method'] = self.method or wrapped.__name__
472 depth = kwargs.pop('_depth', 0)
480 depth = kwargs.pop('_depth', 0)
473
481
474 def callback(context, name, ob):
482 def callback(context, name, ob):
475 config = context.config.with_package(info.module)
483 config = context.config.with_package(info.module)
476 config.add_jsonrpc_method(view=ob, **kwargs)
484 config.add_jsonrpc_method(view=ob, **kwargs)
477
485
478 info = venusian.attach(wrapped, callback, category='pyramid',
486 info = venusian.attach(wrapped, callback, category='pyramid',
479 depth=depth + 1)
487 depth=depth + 1)
480 if info.scope == 'class':
488 if info.scope == 'class':
481 # ensure that attr is set if decorating a class method
489 # ensure that attr is set if decorating a class method
482 kwargs.setdefault('attr', wrapped.__name__)
490 kwargs.setdefault('attr', wrapped.__name__)
483
491
484 kwargs['_info'] = info.codeinfo # fbo action_method
492 kwargs['_info'] = info.codeinfo # fbo action_method
485 return wrapped
493 return wrapped
486
494
487
495
488 class jsonrpc_deprecated_method(object):
496 class jsonrpc_deprecated_method(object):
489 """
497 """
490 Marks method as deprecated, adds log.warning, and inject special key to
498 Marks method as deprecated, adds log.warning, and inject special key to
491 the request variable to mark method as deprecated.
499 the request variable to mark method as deprecated.
492 Also injects special docstring that extract_docs will catch to mark
500 Also injects special docstring that extract_docs will catch to mark
493 method as deprecated.
501 method as deprecated.
494
502
495 :param use_method: specify which method should be used instead of
503 :param use_method: specify which method should be used instead of
496 the decorated one
504 the decorated one
497
505
498 Use like::
506 Use like::
499
507
500 @jsonrpc_method()
508 @jsonrpc_method()
501 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
509 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
502 def old_func(request, apiuser, arg1, arg2):
510 def old_func(request, apiuser, arg1, arg2):
503 ...
511 ...
504 """
512 """
505
513
506 def __init__(self, use_method, deprecated_at_version):
514 def __init__(self, use_method, deprecated_at_version):
507 self.use_method = use_method
515 self.use_method = use_method
508 self.deprecated_at_version = deprecated_at_version
516 self.deprecated_at_version = deprecated_at_version
509 self.deprecated_msg = ''
517 self.deprecated_msg = ''
510
518
511 def __call__(self, func):
519 def __call__(self, func):
512 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
520 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
513 method=self.use_method)
521 method=self.use_method)
514
522
515 docstring = """\n
523 docstring = """\n
516 .. deprecated:: {version}
524 .. deprecated:: {version}
517
525
518 {deprecation_message}
526 {deprecation_message}
519
527
520 {original_docstring}
528 {original_docstring}
521 """
529 """
522 func.__doc__ = docstring.format(
530 func.__doc__ = docstring.format(
523 version=self.deprecated_at_version,
531 version=self.deprecated_at_version,
524 deprecation_message=self.deprecated_msg,
532 deprecation_message=self.deprecated_msg,
525 original_docstring=func.__doc__)
533 original_docstring=func.__doc__)
526 return decorator.decorator(self.__wrapper, func)
534 return decorator.decorator(self.__wrapper, func)
527
535
528 def __wrapper(self, func, *fargs, **fkwargs):
536 def __wrapper(self, func, *fargs, **fkwargs):
529 log.warning('DEPRECATED API CALL on function %s, please '
537 log.warning('DEPRECATED API CALL on function %s, please '
530 'use `%s` instead', func, self.use_method)
538 'use `%s` instead', func, self.use_method)
531 # alter function docstring to mark as deprecated, this is picked up
539 # alter function docstring to mark as deprecated, this is picked up
532 # via fabric file that generates API DOC.
540 # via fabric file that generates API DOC.
533 result = func(*fargs, **fkwargs)
541 result = func(*fargs, **fkwargs)
534
542
535 request = fargs[0]
543 request = fargs[0]
536 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
544 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
537 return result
545 return result
538
546
539
547
540 def add_api_methods(config):
548 def add_api_methods(config):
541 from rhodecode.api.views import (
549 from rhodecode.api.views import (
542 deprecated_api, gist_api, pull_request_api, repo_api, repo_group_api,
550 deprecated_api, gist_api, pull_request_api, repo_api, repo_group_api,
543 server_api, search_api, testing_api, user_api, user_group_api)
551 server_api, search_api, testing_api, user_api, user_group_api)
544
552
545 config.scan('rhodecode.api.views')
553 config.scan('rhodecode.api.views')
546
554
547
555
548 def includeme(config):
556 def includeme(config):
549 plugin_module = 'rhodecode.api'
557 plugin_module = 'rhodecode.api'
550 plugin_settings = get_plugin_settings(
558 plugin_settings = get_plugin_settings(
551 plugin_module, config.registry.settings)
559 plugin_module, config.registry.settings)
552
560
553 if not hasattr(config.registry, 'jsonrpc_methods'):
561 if not hasattr(config.registry, 'jsonrpc_methods'):
554 config.registry.jsonrpc_methods = OrderedDict()
562 config.registry.jsonrpc_methods = OrderedDict()
555
563
556 # match filter by given method only
564 # match filter by given method only
557 config.add_view_predicate('jsonrpc_method', MethodPredicate)
565 config.add_view_predicate('jsonrpc_method', MethodPredicate)
558 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
566 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
559
567
560 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer())
568 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer())
561 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
569 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
562
570
563 config.add_route_predicate(
571 config.add_route_predicate(
564 'jsonrpc_call', RoutePredicate)
572 'jsonrpc_call', RoutePredicate)
565
573
566 config.add_route(
574 config.add_route(
567 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
575 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
568
576
569 # register some exception handling view
577 # register some exception handling view
570 config.add_view(exception_view, context=JSONRPCBaseError)
578 config.add_view(exception_view, context=JSONRPCBaseError)
571 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
579 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
572
580
573 add_api_methods(config)
581 add_api_methods(config)
@@ -1,348 +1,348 b''
1
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.vcs import settings
23 from rhodecode.lib.vcs import settings
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.repo import RepoModel
25 from rhodecode.model.repo import RepoModel
26 from rhodecode.model.user import UserModel
26 from rhodecode.model.user import UserModel
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
28 from rhodecode.api.tests.utils import (
28 from rhodecode.api.tests.utils import (
29 build_data, api_call, assert_ok, assert_error, crash)
29 build_data, api_call, assert_ok, assert_error, crash)
30 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.fixture import Fixture
31 from rhodecode.lib.ext_json import json
31 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.str_utils import safe_str
32 from rhodecode.lib.str_utils import safe_str
33
33
34
34
35 fixture = Fixture()
35 fixture = Fixture()
36
36
37
37
38 @pytest.mark.usefixtures("testuser_api", "app")
38 @pytest.mark.usefixtures("testuser_api", "app")
39 class TestCreateRepo(object):
39 class TestCreateRepo(object):
40
40
41 @pytest.mark.parametrize('given, expected_name, expected_exc', [
41 @pytest.mark.parametrize('given, expected_name, expected_exc', [
42 ('api repo-1', 'api-repo-1', False),
42 ('api repo-1', 'api-repo-1', False),
43 ('api-repo 1-ąć', 'api-repo-1-ąć', False),
43 ('api-repo 1-ąć', 'api-repo-1-ąć', False),
44 (u'unicode-ąć', u'unicode-ąć', False),
44 ('unicode-ąć', u'unicode-ąć', False),
45 ('some repo v1.2', 'some-repo-v1.2', False),
45 ('some repo v1.2', 'some-repo-v1.2', False),
46 ('v2.0', 'v2.0', False),
46 ('v2.0', 'v2.0', False),
47 ])
47 ])
48 def test_api_create_repo(self, backend, given, expected_name, expected_exc):
48 def test_api_create_repo(self, backend, given, expected_name, expected_exc):
49
49
50 id_, params = build_data(
50 id_, params = build_data(
51 self.apikey,
51 self.apikey,
52 'create_repo',
52 'create_repo',
53 repo_name=given,
53 repo_name=given,
54 owner=TEST_USER_ADMIN_LOGIN,
54 owner=TEST_USER_ADMIN_LOGIN,
55 repo_type=backend.alias,
55 repo_type=backend.alias,
56 )
56 )
57 response = api_call(self.app, params)
57 response = api_call(self.app, params)
58
58
59 ret = {
59 ret = {
60 'msg': 'Created new repository `%s`' % (expected_name,),
60 'msg': 'Created new repository `%s`' % (expected_name,),
61 'success': True,
61 'success': True,
62 'task': None,
62 'task': None,
63 }
63 }
64 expected = ret
64 expected = ret
65 assert_ok(id_, expected, given=response.body)
65 assert_ok(id_, expected, given=response.body)
66
66
67 repo = RepoModel().get_by_repo_name(safe_str(expected_name))
67 repo = RepoModel().get_by_repo_name(safe_str(expected_name))
68 assert repo is not None
68 assert repo is not None
69
69
70 id_, params = build_data(self.apikey, 'get_repo', repoid=expected_name)
70 id_, params = build_data(self.apikey, 'get_repo', repoid=expected_name)
71 response = api_call(self.app, params)
71 response = api_call(self.app, params)
72 body = json.loads(response.body)
72 body = json.loads(response.body)
73
73
74 assert body['result']['enable_downloads'] is False
74 assert body['result']['enable_downloads'] is False
75 assert body['result']['enable_locking'] is False
75 assert body['result']['enable_locking'] is False
76 assert body['result']['enable_statistics'] is False
76 assert body['result']['enable_statistics'] is False
77
77
78 fixture.destroy_repo(safe_str(expected_name))
78 fixture.destroy_repo(safe_str(expected_name))
79
79
80 def test_api_create_restricted_repo_type(self, backend):
80 def test_api_create_restricted_repo_type(self, backend):
81 repo_name = 'api-repo-type-{0}'.format(backend.alias)
81 repo_name = 'api-repo-type-{0}'.format(backend.alias)
82 id_, params = build_data(
82 id_, params = build_data(
83 self.apikey,
83 self.apikey,
84 'create_repo',
84 'create_repo',
85 repo_name=repo_name,
85 repo_name=repo_name,
86 owner=TEST_USER_ADMIN_LOGIN,
86 owner=TEST_USER_ADMIN_LOGIN,
87 repo_type=backend.alias,
87 repo_type=backend.alias,
88 )
88 )
89 git_backend = settings.BACKENDS['git']
89 git_backend = settings.BACKENDS['git']
90 with mock.patch(
90 with mock.patch(
91 'rhodecode.lib.vcs.settings.BACKENDS', {'git': git_backend}):
91 'rhodecode.lib.vcs.settings.BACKENDS', {'git': git_backend}):
92 response = api_call(self.app, params)
92 response = api_call(self.app, params)
93
93
94 repo = RepoModel().get_by_repo_name(repo_name)
94 repo = RepoModel().get_by_repo_name(repo_name)
95
95
96 if backend.alias == 'git':
96 if backend.alias == 'git':
97 assert repo is not None
97 assert repo is not None
98 expected = {
98 expected = {
99 'msg': 'Created new repository `{0}`'.format(repo_name,),
99 'msg': 'Created new repository `{0}`'.format(repo_name,),
100 'success': True,
100 'success': True,
101 'task': None,
101 'task': None,
102 }
102 }
103 assert_ok(id_, expected, given=response.body)
103 assert_ok(id_, expected, given=response.body)
104 else:
104 else:
105 assert repo is None
105 assert repo is None
106
106
107 fixture.destroy_repo(repo_name)
107 fixture.destroy_repo(repo_name)
108
108
109 def test_api_create_repo_with_booleans(self, backend):
109 def test_api_create_repo_with_booleans(self, backend):
110 repo_name = 'api-repo-2'
110 repo_name = 'api-repo-2'
111 id_, params = build_data(
111 id_, params = build_data(
112 self.apikey,
112 self.apikey,
113 'create_repo',
113 'create_repo',
114 repo_name=repo_name,
114 repo_name=repo_name,
115 owner=TEST_USER_ADMIN_LOGIN,
115 owner=TEST_USER_ADMIN_LOGIN,
116 repo_type=backend.alias,
116 repo_type=backend.alias,
117 enable_statistics=True,
117 enable_statistics=True,
118 enable_locking=True,
118 enable_locking=True,
119 enable_downloads=True
119 enable_downloads=True
120 )
120 )
121 response = api_call(self.app, params)
121 response = api_call(self.app, params)
122
122
123 repo = RepoModel().get_by_repo_name(repo_name)
123 repo = RepoModel().get_by_repo_name(repo_name)
124
124
125 assert repo is not None
125 assert repo is not None
126 ret = {
126 ret = {
127 'msg': 'Created new repository `%s`' % (repo_name,),
127 'msg': 'Created new repository `%s`' % (repo_name,),
128 'success': True,
128 'success': True,
129 'task': None,
129 'task': None,
130 }
130 }
131 expected = ret
131 expected = ret
132 assert_ok(id_, expected, given=response.body)
132 assert_ok(id_, expected, given=response.body)
133
133
134 id_, params = build_data(self.apikey, 'get_repo', repoid=repo_name)
134 id_, params = build_data(self.apikey, 'get_repo', repoid=repo_name)
135 response = api_call(self.app, params)
135 response = api_call(self.app, params)
136 body = json.loads(response.body)
136 body = json.loads(response.body)
137
137
138 assert body['result']['enable_downloads'] is True
138 assert body['result']['enable_downloads'] is True
139 assert body['result']['enable_locking'] is True
139 assert body['result']['enable_locking'] is True
140 assert body['result']['enable_statistics'] is True
140 assert body['result']['enable_statistics'] is True
141
141
142 fixture.destroy_repo(repo_name)
142 fixture.destroy_repo(repo_name)
143
143
144 def test_api_create_repo_in_group(self, backend):
144 def test_api_create_repo_in_group(self, backend):
145 repo_group_name = 'my_gr'
145 repo_group_name = 'my_gr'
146 # create the parent
146 # create the parent
147 fixture.create_repo_group(repo_group_name)
147 fixture.create_repo_group(repo_group_name)
148
148
149 repo_name = '%s/api-repo-gr' % (repo_group_name,)
149 repo_name = '%s/api-repo-gr' % (repo_group_name,)
150 id_, params = build_data(
150 id_, params = build_data(
151 self.apikey, 'create_repo',
151 self.apikey, 'create_repo',
152 repo_name=repo_name,
152 repo_name=repo_name,
153 owner=TEST_USER_ADMIN_LOGIN,
153 owner=TEST_USER_ADMIN_LOGIN,
154 repo_type=backend.alias,)
154 repo_type=backend.alias,)
155 response = api_call(self.app, params)
155 response = api_call(self.app, params)
156 repo = RepoModel().get_by_repo_name(repo_name)
156 repo = RepoModel().get_by_repo_name(repo_name)
157 assert repo is not None
157 assert repo is not None
158 assert repo.group is not None
158 assert repo.group is not None
159
159
160 ret = {
160 ret = {
161 'msg': 'Created new repository `%s`' % (repo_name,),
161 'msg': 'Created new repository `%s`' % (repo_name,),
162 'success': True,
162 'success': True,
163 'task': None,
163 'task': None,
164 }
164 }
165 expected = ret
165 expected = ret
166 assert_ok(id_, expected, given=response.body)
166 assert_ok(id_, expected, given=response.body)
167 fixture.destroy_repo(repo_name)
167 fixture.destroy_repo(repo_name)
168 fixture.destroy_repo_group(repo_group_name)
168 fixture.destroy_repo_group(repo_group_name)
169
169
170 def test_create_repo_in_group_that_doesnt_exist(self, backend, user_util):
170 def test_create_repo_in_group_that_doesnt_exist(self, backend, user_util):
171 repo_group_name = 'fake_group'
171 repo_group_name = 'fake_group'
172
172
173 repo_name = '%s/api-repo-gr' % (repo_group_name,)
173 repo_name = '%s/api-repo-gr' % (repo_group_name,)
174 id_, params = build_data(
174 id_, params = build_data(
175 self.apikey, 'create_repo',
175 self.apikey, 'create_repo',
176 repo_name=repo_name,
176 repo_name=repo_name,
177 owner=TEST_USER_ADMIN_LOGIN,
177 owner=TEST_USER_ADMIN_LOGIN,
178 repo_type=backend.alias,)
178 repo_type=backend.alias,)
179 response = api_call(self.app, params)
179 response = api_call(self.app, params)
180
180
181 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
181 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
182 repo_group_name)}
182 repo_group_name)}
183 assert_error(id_, expected, given=response.body)
183 assert_error(id_, expected, given=response.body)
184
184
185 def test_api_create_repo_unknown_owner(self, backend):
185 def test_api_create_repo_unknown_owner(self, backend):
186 repo_name = 'api-repo-2'
186 repo_name = 'api-repo-2'
187 owner = 'i-dont-exist'
187 owner = 'i-dont-exist'
188 id_, params = build_data(
188 id_, params = build_data(
189 self.apikey, 'create_repo',
189 self.apikey, 'create_repo',
190 repo_name=repo_name,
190 repo_name=repo_name,
191 owner=owner,
191 owner=owner,
192 repo_type=backend.alias)
192 repo_type=backend.alias)
193 response = api_call(self.app, params)
193 response = api_call(self.app, params)
194 expected = 'user `%s` does not exist' % (owner,)
194 expected = 'user `%s` does not exist' % (owner,)
195 assert_error(id_, expected, given=response.body)
195 assert_error(id_, expected, given=response.body)
196
196
197 def test_api_create_repo_dont_specify_owner(self, backend):
197 def test_api_create_repo_dont_specify_owner(self, backend):
198 repo_name = 'api-repo-3'
198 repo_name = 'api-repo-3'
199 id_, params = build_data(
199 id_, params = build_data(
200 self.apikey, 'create_repo',
200 self.apikey, 'create_repo',
201 repo_name=repo_name,
201 repo_name=repo_name,
202 repo_type=backend.alias)
202 repo_type=backend.alias)
203 response = api_call(self.app, params)
203 response = api_call(self.app, params)
204
204
205 repo = RepoModel().get_by_repo_name(repo_name)
205 repo = RepoModel().get_by_repo_name(repo_name)
206 assert repo is not None
206 assert repo is not None
207 ret = {
207 ret = {
208 'msg': 'Created new repository `%s`' % (repo_name,),
208 'msg': 'Created new repository `%s`' % (repo_name,),
209 'success': True,
209 'success': True,
210 'task': None,
210 'task': None,
211 }
211 }
212 expected = ret
212 expected = ret
213 assert_ok(id_, expected, given=response.body)
213 assert_ok(id_, expected, given=response.body)
214 fixture.destroy_repo(repo_name)
214 fixture.destroy_repo(repo_name)
215
215
216 def test_api_create_repo_by_non_admin(self, backend):
216 def test_api_create_repo_by_non_admin(self, backend):
217 repo_name = 'api-repo-4'
217 repo_name = 'api-repo-4'
218 id_, params = build_data(
218 id_, params = build_data(
219 self.apikey_regular, 'create_repo',
219 self.apikey_regular, 'create_repo',
220 repo_name=repo_name,
220 repo_name=repo_name,
221 repo_type=backend.alias)
221 repo_type=backend.alias)
222 response = api_call(self.app, params)
222 response = api_call(self.app, params)
223
223
224 repo = RepoModel().get_by_repo_name(repo_name)
224 repo = RepoModel().get_by_repo_name(repo_name)
225 assert repo is not None
225 assert repo is not None
226 ret = {
226 ret = {
227 'msg': 'Created new repository `%s`' % (repo_name,),
227 'msg': 'Created new repository `%s`' % (repo_name,),
228 'success': True,
228 'success': True,
229 'task': None,
229 'task': None,
230 }
230 }
231 expected = ret
231 expected = ret
232 assert_ok(id_, expected, given=response.body)
232 assert_ok(id_, expected, given=response.body)
233 fixture.destroy_repo(repo_name)
233 fixture.destroy_repo(repo_name)
234
234
235 def test_api_create_repo_by_non_admin_specify_owner(self, backend):
235 def test_api_create_repo_by_non_admin_specify_owner(self, backend):
236 repo_name = 'api-repo-5'
236 repo_name = 'api-repo-5'
237 owner = 'i-dont-exist'
237 owner = 'i-dont-exist'
238 id_, params = build_data(
238 id_, params = build_data(
239 self.apikey_regular, 'create_repo',
239 self.apikey_regular, 'create_repo',
240 repo_name=repo_name,
240 repo_name=repo_name,
241 repo_type=backend.alias,
241 repo_type=backend.alias,
242 owner=owner)
242 owner=owner)
243 response = api_call(self.app, params)
243 response = api_call(self.app, params)
244
244
245 expected = 'Only RhodeCode super-admin can specify `owner` param'
245 expected = 'Only RhodeCode super-admin can specify `owner` param'
246 assert_error(id_, expected, given=response.body)
246 assert_error(id_, expected, given=response.body)
247 fixture.destroy_repo(repo_name)
247 fixture.destroy_repo(repo_name)
248
248
249 def test_api_create_repo_by_non_admin_no_parent_group_perms(self, backend):
249 def test_api_create_repo_by_non_admin_no_parent_group_perms(self, backend):
250 repo_group_name = 'no-access'
250 repo_group_name = 'no-access'
251 fixture.create_repo_group(repo_group_name)
251 fixture.create_repo_group(repo_group_name)
252 repo_name = 'no-access/api-repo'
252 repo_name = 'no-access/api-repo'
253
253
254 id_, params = build_data(
254 id_, params = build_data(
255 self.apikey_regular, 'create_repo',
255 self.apikey_regular, 'create_repo',
256 repo_name=repo_name,
256 repo_name=repo_name,
257 repo_type=backend.alias)
257 repo_type=backend.alias)
258 response = api_call(self.app, params)
258 response = api_call(self.app, params)
259
259
260 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
260 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
261 repo_group_name)}
261 repo_group_name)}
262 assert_error(id_, expected, given=response.body)
262 assert_error(id_, expected, given=response.body)
263 fixture.destroy_repo_group(repo_group_name)
263 fixture.destroy_repo_group(repo_group_name)
264 fixture.destroy_repo(repo_name)
264 fixture.destroy_repo(repo_name)
265
265
266 def test_api_create_repo_non_admin_no_permission_to_create_to_root_level(
266 def test_api_create_repo_non_admin_no_permission_to_create_to_root_level(
267 self, backend, user_util):
267 self, backend, user_util):
268
268
269 regular_user = user_util.create_user()
269 regular_user = user_util.create_user()
270 regular_user_api_key = regular_user.api_key
270 regular_user_api_key = regular_user.api_key
271
271
272 usr = UserModel().get_by_username(regular_user.username)
272 usr = UserModel().get_by_username(regular_user.username)
273 usr.inherit_default_permissions = False
273 usr.inherit_default_permissions = False
274 Session().add(usr)
274 Session().add(usr)
275
275
276 repo_name = backend.new_repo_name()
276 repo_name = backend.new_repo_name()
277 id_, params = build_data(
277 id_, params = build_data(
278 regular_user_api_key, 'create_repo',
278 regular_user_api_key, 'create_repo',
279 repo_name=repo_name,
279 repo_name=repo_name,
280 repo_type=backend.alias)
280 repo_type=backend.alias)
281 response = api_call(self.app, params)
281 response = api_call(self.app, params)
282 expected = {
282 expected = {
283 "repo_name": "You do not have the permission to "
283 "repo_name": "You do not have the permission to "
284 "store repositories in the root location."}
284 "store repositories in the root location."}
285 assert_error(id_, expected, given=response.body)
285 assert_error(id_, expected, given=response.body)
286
286
287 def test_api_create_repo_exists(self, backend):
287 def test_api_create_repo_exists(self, backend):
288 repo_name = backend.repo_name
288 repo_name = backend.repo_name
289 id_, params = build_data(
289 id_, params = build_data(
290 self.apikey, 'create_repo',
290 self.apikey, 'create_repo',
291 repo_name=repo_name,
291 repo_name=repo_name,
292 owner=TEST_USER_ADMIN_LOGIN,
292 owner=TEST_USER_ADMIN_LOGIN,
293 repo_type=backend.alias,)
293 repo_type=backend.alias,)
294 response = api_call(self.app, params)
294 response = api_call(self.app, params)
295 expected = {
295 expected = {
296 'unique_repo_name': 'Repository with name `{}` already exists'.format(
296 'unique_repo_name': 'Repository with name `{}` already exists'.format(
297 repo_name)}
297 repo_name)}
298 assert_error(id_, expected, given=response.body)
298 assert_error(id_, expected, given=response.body)
299
299
300 @mock.patch.object(RepoModel, 'create', crash)
300 @mock.patch.object(RepoModel, 'create', crash)
301 def test_api_create_repo_exception_occurred(self, backend):
301 def test_api_create_repo_exception_occurred(self, backend):
302 repo_name = 'api-repo-6'
302 repo_name = 'api-repo-6'
303 id_, params = build_data(
303 id_, params = build_data(
304 self.apikey, 'create_repo',
304 self.apikey, 'create_repo',
305 repo_name=repo_name,
305 repo_name=repo_name,
306 owner=TEST_USER_ADMIN_LOGIN,
306 owner=TEST_USER_ADMIN_LOGIN,
307 repo_type=backend.alias,)
307 repo_type=backend.alias,)
308 response = api_call(self.app, params)
308 response = api_call(self.app, params)
309 expected = 'failed to create repository `%s`' % (repo_name,)
309 expected = 'failed to create repository `%s`' % (repo_name,)
310 assert_error(id_, expected, given=response.body)
310 assert_error(id_, expected, given=response.body)
311
311
312 @pytest.mark.parametrize('parent_group, dirty_name, expected_name', [
312 @pytest.mark.parametrize('parent_group, dirty_name, expected_name', [
313 (None, 'foo bar x', 'foo-bar-x'),
313 (None, 'foo bar x', 'foo-bar-x'),
314 ('foo', '/foo//bar x', 'foo/bar-x'),
314 ('foo', '/foo//bar x', 'foo/bar-x'),
315 ('foo-bar', 'foo-bar //bar x', 'foo-bar/bar-x'),
315 ('foo-bar', 'foo-bar //bar x', 'foo-bar/bar-x'),
316 ])
316 ])
317 def test_create_repo_with_extra_slashes_in_name(
317 def test_create_repo_with_extra_slashes_in_name(
318 self, backend, parent_group, dirty_name, expected_name):
318 self, backend, parent_group, dirty_name, expected_name):
319
319
320 if parent_group:
320 if parent_group:
321 gr = fixture.create_repo_group(parent_group)
321 gr = fixture.create_repo_group(parent_group)
322 assert gr.group_name == parent_group
322 assert gr.group_name == parent_group
323
323
324 id_, params = build_data(
324 id_, params = build_data(
325 self.apikey, 'create_repo',
325 self.apikey, 'create_repo',
326 repo_name=dirty_name,
326 repo_name=dirty_name,
327 repo_type=backend.alias,
327 repo_type=backend.alias,
328 owner=TEST_USER_ADMIN_LOGIN,)
328 owner=TEST_USER_ADMIN_LOGIN,)
329 response = api_call(self.app, params)
329 response = api_call(self.app, params)
330 expected ={
330 expected ={
331 "msg": "Created new repository `{}`".format(expected_name),
331 "msg": "Created new repository `{}`".format(expected_name),
332 "task": None,
332 "task": None,
333 "success": True
333 "success": True
334 }
334 }
335 assert_ok(id_, expected, response.body)
335 assert_ok(id_, expected, response.body)
336
336
337 repo = RepoModel().get_by_repo_name(expected_name)
337 repo = RepoModel().get_by_repo_name(expected_name)
338 assert repo is not None
338 assert repo is not None
339
339
340 expected = {
340 expected = {
341 'msg': 'Created new repository `%s`' % (expected_name,),
341 'msg': 'Created new repository `%s`' % (expected_name,),
342 'success': True,
342 'success': True,
343 'task': None,
343 'task': None,
344 }
344 }
345 assert_ok(id_, expected, given=response.body)
345 assert_ok(id_, expected, given=response.body)
346 fixture.destroy_repo(expected_name)
346 fixture.destroy_repo(expected_name)
347 if parent_group:
347 if parent_group:
348 fixture.destroy_repo_group(parent_group)
348 fixture.destroy_repo_group(parent_group)
@@ -1,288 +1,288 b''
1
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.meta import Session
23 from rhodecode.model.meta import Session
24 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_ok, assert_error, crash)
28 build_data, api_call, assert_ok, assert_error, crash)
29 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.fixture import Fixture
30
30
31
31
32 fixture = Fixture()
32 fixture = Fixture()
33
33
34
34
35 @pytest.mark.usefixtures("testuser_api", "app")
35 @pytest.mark.usefixtures("testuser_api", "app")
36 class TestCreateRepoGroup(object):
36 class TestCreateRepoGroup(object):
37 def test_api_create_repo_group(self):
37 def test_api_create_repo_group(self):
38 repo_group_name = 'api-repo-group'
38 repo_group_name = 'api-repo-group'
39
39
40 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
40 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
41 assert repo_group is None
41 assert repo_group is None
42
42
43 id_, params = build_data(
43 id_, params = build_data(
44 self.apikey, 'create_repo_group',
44 self.apikey, 'create_repo_group',
45 group_name=repo_group_name,
45 group_name=repo_group_name,
46 owner=TEST_USER_ADMIN_LOGIN,)
46 owner=TEST_USER_ADMIN_LOGIN,)
47 response = api_call(self.app, params)
47 response = api_call(self.app, params)
48
48
49 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
49 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
50 assert repo_group is not None
50 assert repo_group is not None
51 ret = {
51 ret = {
52 'msg': 'Created new repo group `%s`' % (repo_group_name,),
52 'msg': 'Created new repo group `%s`' % (repo_group_name,),
53 'repo_group': repo_group.get_api_data()
53 'repo_group': repo_group.get_api_data()
54 }
54 }
55 expected = ret
55 expected = ret
56 try:
56 try:
57 assert_ok(id_, expected, given=response.body)
57 assert_ok(id_, expected, given=response.body)
58 finally:
58 finally:
59 fixture.destroy_repo_group(repo_group_name)
59 fixture.destroy_repo_group(repo_group_name)
60
60
61 def test_api_create_repo_group_in_another_group(self):
61 def test_api_create_repo_group_in_another_group(self):
62 repo_group_name = 'api-repo-group'
62 repo_group_name = 'api-repo-group'
63
63
64 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
64 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
65 assert repo_group is None
65 assert repo_group is None
66 # create the parent
66 # create the parent
67 fixture.create_repo_group(repo_group_name)
67 fixture.create_repo_group(repo_group_name)
68
68
69 full_repo_group_name = repo_group_name+'/'+repo_group_name
69 full_repo_group_name = repo_group_name+'/'+repo_group_name
70 id_, params = build_data(
70 id_, params = build_data(
71 self.apikey, 'create_repo_group',
71 self.apikey, 'create_repo_group',
72 group_name=full_repo_group_name,
72 group_name=full_repo_group_name,
73 owner=TEST_USER_ADMIN_LOGIN,
73 owner=TEST_USER_ADMIN_LOGIN,
74 copy_permissions=True)
74 copy_permissions=True)
75 response = api_call(self.app, params)
75 response = api_call(self.app, params)
76
76
77 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
77 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
78 assert repo_group is not None
78 assert repo_group is not None
79 ret = {
79 ret = {
80 'msg': 'Created new repo group `%s`' % (full_repo_group_name,),
80 'msg': 'Created new repo group `%s`' % (full_repo_group_name,),
81 'repo_group': repo_group.get_api_data()
81 'repo_group': repo_group.get_api_data()
82 }
82 }
83 expected = ret
83 expected = ret
84 try:
84 try:
85 assert_ok(id_, expected, given=response.body)
85 assert_ok(id_, expected, given=response.body)
86 finally:
86 finally:
87 fixture.destroy_repo_group(full_repo_group_name)
87 fixture.destroy_repo_group(full_repo_group_name)
88 fixture.destroy_repo_group(repo_group_name)
88 fixture.destroy_repo_group(repo_group_name)
89
89
90 def test_api_create_repo_group_in_another_group_not_existing(self):
90 def test_api_create_repo_group_in_another_group_not_existing(self):
91 repo_group_name = 'api-repo-group-no'
91 repo_group_name = 'api-repo-group-no'
92
92
93 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
93 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
94 assert repo_group is None
94 assert repo_group is None
95
95
96 full_repo_group_name = repo_group_name+'/'+repo_group_name
96 full_repo_group_name = repo_group_name+'/'+repo_group_name
97 id_, params = build_data(
97 id_, params = build_data(
98 self.apikey, 'create_repo_group',
98 self.apikey, 'create_repo_group',
99 group_name=full_repo_group_name,
99 group_name=full_repo_group_name,
100 owner=TEST_USER_ADMIN_LOGIN,
100 owner=TEST_USER_ADMIN_LOGIN,
101 copy_permissions=True)
101 copy_permissions=True)
102 response = api_call(self.app, params)
102 response = api_call(self.app, params)
103 expected = {
103 expected = {
104 'repo_group':
104 'repo_group':
105 'Parent repository group `{}` does not exist'.format(
105 'Parent repository group `{}` does not exist'.format(
106 repo_group_name)}
106 repo_group_name)}
107 assert_error(id_, expected, given=response.body)
107 assert_error(id_, expected, given=response.body)
108
108
109 def test_api_create_repo_group_that_exists(self):
109 def test_api_create_repo_group_that_exists(self):
110 repo_group_name = 'api-repo-group'
110 repo_group_name = 'api-repo-group'
111
111
112 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
112 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
113 assert repo_group is None
113 assert repo_group is None
114
114
115 fixture.create_repo_group(repo_group_name)
115 fixture.create_repo_group(repo_group_name)
116 id_, params = build_data(
116 id_, params = build_data(
117 self.apikey, 'create_repo_group',
117 self.apikey, 'create_repo_group',
118 group_name=repo_group_name,
118 group_name=repo_group_name,
119 owner=TEST_USER_ADMIN_LOGIN,)
119 owner=TEST_USER_ADMIN_LOGIN,)
120 response = api_call(self.app, params)
120 response = api_call(self.app, params)
121 expected = {
121 expected = {
122 'unique_repo_group_name':
122 'unique_repo_group_name':
123 'Repository group with name `{}` already exists'.format(
123 'Repository group with name `{}` already exists'.format(
124 repo_group_name)}
124 repo_group_name)}
125 try:
125 try:
126 assert_error(id_, expected, given=response.body)
126 assert_error(id_, expected, given=response.body)
127 finally:
127 finally:
128 fixture.destroy_repo_group(repo_group_name)
128 fixture.destroy_repo_group(repo_group_name)
129
129
130 def test_api_create_repo_group_regular_user_wit_root_location_perms(
130 def test_api_create_repo_group_regular_user_wit_root_location_perms(
131 self, user_util):
131 self, user_util):
132 regular_user = user_util.create_user()
132 regular_user = user_util.create_user()
133 regular_user_api_key = regular_user.api_key
133 regular_user_api_key = regular_user.api_key
134
134
135 repo_group_name = 'api-repo-group-by-regular-user'
135 repo_group_name = 'api-repo-group-by-regular-user'
136
136
137 usr = UserModel().get_by_username(regular_user.username)
137 usr = UserModel().get_by_username(regular_user.username)
138 usr.inherit_default_permissions = False
138 usr.inherit_default_permissions = False
139 Session().add(usr)
139 Session().add(usr)
140
140
141 UserModel().grant_perm(
141 UserModel().grant_perm(
142 regular_user.username, 'hg.repogroup.create.true')
142 regular_user.username, 'hg.repogroup.create.true')
143 Session().commit()
143 Session().commit()
144
144
145 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
145 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
146 assert repo_group is None
146 assert repo_group is None
147
147
148 id_, params = build_data(
148 id_, params = build_data(
149 regular_user_api_key, 'create_repo_group',
149 regular_user_api_key, 'create_repo_group',
150 group_name=repo_group_name)
150 group_name=repo_group_name)
151 response = api_call(self.app, params)
151 response = api_call(self.app, params)
152
152
153 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
153 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
154 assert repo_group is not None
154 assert repo_group is not None
155 expected = {
155 expected = {
156 'msg': 'Created new repo group `%s`' % (repo_group_name,),
156 'msg': 'Created new repo group `%s`' % (repo_group_name,),
157 'repo_group': repo_group.get_api_data()
157 'repo_group': repo_group.get_api_data()
158 }
158 }
159 try:
159 try:
160 assert_ok(id_, expected, given=response.body)
160 assert_ok(id_, expected, given=response.body)
161 finally:
161 finally:
162 fixture.destroy_repo_group(repo_group_name)
162 fixture.destroy_repo_group(repo_group_name)
163
163
164 def test_api_create_repo_group_regular_user_with_admin_perms_to_parent(
164 def test_api_create_repo_group_regular_user_with_admin_perms_to_parent(
165 self, user_util):
165 self, user_util):
166
166
167 repo_group_name = 'api-repo-group-parent'
167 repo_group_name = 'api-repo-group-parent'
168
168
169 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
169 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
170 assert repo_group is None
170 assert repo_group is None
171 # create the parent
171 # create the parent
172 fixture.create_repo_group(repo_group_name)
172 fixture.create_repo_group(repo_group_name)
173
173
174 # user perms
174 # user perms
175 regular_user = user_util.create_user()
175 regular_user = user_util.create_user()
176 regular_user_api_key = regular_user.api_key
176 regular_user_api_key = regular_user.api_key
177
177
178 usr = UserModel().get_by_username(regular_user.username)
178 usr = UserModel().get_by_username(regular_user.username)
179 usr.inherit_default_permissions = False
179 usr.inherit_default_permissions = False
180 Session().add(usr)
180 Session().add(usr)
181
181
182 RepoGroupModel().grant_user_permission(
182 RepoGroupModel().grant_user_permission(
183 repo_group_name, regular_user.username, 'group.admin')
183 repo_group_name, regular_user.username, 'group.admin')
184 Session().commit()
184 Session().commit()
185
185
186 full_repo_group_name = repo_group_name + '/' + repo_group_name
186 full_repo_group_name = repo_group_name + '/' + repo_group_name
187 id_, params = build_data(
187 id_, params = build_data(
188 regular_user_api_key, 'create_repo_group',
188 regular_user_api_key, 'create_repo_group',
189 group_name=full_repo_group_name)
189 group_name=full_repo_group_name)
190 response = api_call(self.app, params)
190 response = api_call(self.app, params)
191
191
192 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
192 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
193 assert repo_group is not None
193 assert repo_group is not None
194 expected = {
194 expected = {
195 'msg': 'Created new repo group `{}`'.format(full_repo_group_name),
195 'msg': 'Created new repo group `{}`'.format(full_repo_group_name),
196 'repo_group': repo_group.get_api_data()
196 'repo_group': repo_group.get_api_data()
197 }
197 }
198 try:
198 try:
199 assert_ok(id_, expected, given=response.body)
199 assert_ok(id_, expected, given=response.body)
200 finally:
200 finally:
201 fixture.destroy_repo_group(full_repo_group_name)
201 fixture.destroy_repo_group(full_repo_group_name)
202 fixture.destroy_repo_group(repo_group_name)
202 fixture.destroy_repo_group(repo_group_name)
203
203
204 def test_api_create_repo_group_regular_user_no_permission_to_create_to_root_level(self):
204 def test_api_create_repo_group_regular_user_no_permission_to_create_to_root_level(self):
205 repo_group_name = 'api-repo-group'
205 repo_group_name = 'api-repo-group'
206
206
207 id_, params = build_data(
207 id_, params = build_data(
208 self.apikey_regular, 'create_repo_group',
208 self.apikey_regular, 'create_repo_group',
209 group_name=repo_group_name)
209 group_name=repo_group_name)
210 response = api_call(self.app, params)
210 response = api_call(self.app, params)
211
211
212 expected = {
212 expected = {
213 'repo_group':
213 'repo_group':
214 u'You do not have the permission to store '
214 'You do not have the permission to store '
215 u'repository groups in the root location.'}
215 'repository groups in the root location.'}
216 assert_error(id_, expected, given=response.body)
216 assert_error(id_, expected, given=response.body)
217
217
218 def test_api_create_repo_group_regular_user_no_parent_group_perms(self):
218 def test_api_create_repo_group_regular_user_no_parent_group_perms(self):
219 repo_group_name = 'api-repo-group-regular-user'
219 repo_group_name = 'api-repo-group-regular-user'
220
220
221 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
221 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
222 assert repo_group is None
222 assert repo_group is None
223 # create the parent
223 # create the parent
224 fixture.create_repo_group(repo_group_name)
224 fixture.create_repo_group(repo_group_name)
225
225
226 full_repo_group_name = repo_group_name+'/'+repo_group_name
226 full_repo_group_name = repo_group_name+'/'+repo_group_name
227
227
228 id_, params = build_data(
228 id_, params = build_data(
229 self.apikey_regular, 'create_repo_group',
229 self.apikey_regular, 'create_repo_group',
230 group_name=full_repo_group_name)
230 group_name=full_repo_group_name)
231 response = api_call(self.app, params)
231 response = api_call(self.app, params)
232
232
233 expected = {
233 expected = {
234 'repo_group':
234 'repo_group':
235 u"You do not have the permissions to store "
235 "You do not have the permissions to store "
236 u"repository groups inside repository group `{}`".format(repo_group_name)}
236 "repository groups inside repository group `{}`".format(repo_group_name)}
237 try:
237 try:
238 assert_error(id_, expected, given=response.body)
238 assert_error(id_, expected, given=response.body)
239 finally:
239 finally:
240 fixture.destroy_repo_group(repo_group_name)
240 fixture.destroy_repo_group(repo_group_name)
241
241
242 def test_api_create_repo_group_regular_user_no_permission_to_specify_owner(
242 def test_api_create_repo_group_regular_user_no_permission_to_specify_owner(
243 self):
243 self):
244 repo_group_name = 'api-repo-group'
244 repo_group_name = 'api-repo-group'
245
245
246 id_, params = build_data(
246 id_, params = build_data(
247 self.apikey_regular, 'create_repo_group',
247 self.apikey_regular, 'create_repo_group',
248 group_name=repo_group_name,
248 group_name=repo_group_name,
249 owner=TEST_USER_ADMIN_LOGIN,)
249 owner=TEST_USER_ADMIN_LOGIN,)
250 response = api_call(self.app, params)
250 response = api_call(self.app, params)
251
251
252 expected = "Only RhodeCode super-admin can specify `owner` param"
252 expected = "Only RhodeCode super-admin can specify `owner` param"
253 assert_error(id_, expected, given=response.body)
253 assert_error(id_, expected, given=response.body)
254
254
255 @mock.patch.object(RepoGroupModel, 'create', crash)
255 @mock.patch.object(RepoGroupModel, 'create', crash)
256 def test_api_create_repo_group_exception_occurred(self):
256 def test_api_create_repo_group_exception_occurred(self):
257 repo_group_name = 'api-repo-group'
257 repo_group_name = 'api-repo-group'
258
258
259 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
259 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
260 assert repo_group is None
260 assert repo_group is None
261
261
262 id_, params = build_data(
262 id_, params = build_data(
263 self.apikey, 'create_repo_group',
263 self.apikey, 'create_repo_group',
264 group_name=repo_group_name,
264 group_name=repo_group_name,
265 owner=TEST_USER_ADMIN_LOGIN,)
265 owner=TEST_USER_ADMIN_LOGIN,)
266 response = api_call(self.app, params)
266 response = api_call(self.app, params)
267 expected = 'failed to create repo group `%s`' % (repo_group_name,)
267 expected = 'failed to create repo group `%s`' % (repo_group_name,)
268 assert_error(id_, expected, given=response.body)
268 assert_error(id_, expected, given=response.body)
269
269
270 def test_create_group_with_extra_slashes_in_name(self, user_util):
270 def test_create_group_with_extra_slashes_in_name(self, user_util):
271 existing_repo_group = user_util.create_repo_group()
271 existing_repo_group = user_util.create_repo_group()
272 dirty_group_name = '//{}//group2//'.format(
272 dirty_group_name = '//{}//group2//'.format(
273 existing_repo_group.group_name)
273 existing_repo_group.group_name)
274 cleaned_group_name = '{}/group2'.format(
274 cleaned_group_name = '{}/group2'.format(
275 existing_repo_group.group_name)
275 existing_repo_group.group_name)
276
276
277 id_, params = build_data(
277 id_, params = build_data(
278 self.apikey, 'create_repo_group',
278 self.apikey, 'create_repo_group',
279 group_name=dirty_group_name,
279 group_name=dirty_group_name,
280 owner=TEST_USER_ADMIN_LOGIN,)
280 owner=TEST_USER_ADMIN_LOGIN,)
281 response = api_call(self.app, params)
281 response = api_call(self.app, params)
282 repo_group = RepoGroupModel.cls.get_by_group_name(cleaned_group_name)
282 repo_group = RepoGroupModel.cls.get_by_group_name(cleaned_group_name)
283 expected = {
283 expected = {
284 'msg': 'Created new repo group `%s`' % (cleaned_group_name,),
284 'msg': 'Created new repo group `%s`' % (cleaned_group_name,),
285 'repo_group': repo_group.get_api_data()
285 'repo_group': repo_group.get_api_data()
286 }
286 }
287 assert_ok(id_, expected, given=response.body)
287 assert_ok(id_, expected, given=response.body)
288 fixture.destroy_repo_group(cleaned_group_name)
288 fixture.destroy_repo_group(cleaned_group_name)
@@ -1,101 +1,101 b''
1
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.str_utils import safe_bytes
23 from rhodecode.lib.str_utils import safe_bytes
24 from rhodecode.model.db import Gist
24 from rhodecode.model.db import Gist
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok)
26 build_data, api_call, assert_error, assert_ok)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestApiGetGist(object):
30 class TestApiGetGist(object):
31 def test_api_get_gist(self, gist_util, http_host_only_stub):
31 def test_api_get_gist(self, gist_util, http_host_only_stub):
32 gist = gist_util.create_gist()
32 gist = gist_util.create_gist()
33 gist_id = gist.gist_access_id
33 gist_id = gist.gist_access_id
34 gist_created_on = gist.created_on
34 gist_created_on = gist.created_on
35 gist_modified_at = gist.modified_at
35 gist_modified_at = gist.modified_at
36 id_, params = build_data(
36 id_, params = build_data(
37 self.apikey, 'get_gist', gistid=gist_id, )
37 self.apikey, 'get_gist', gistid=gist_id, )
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39
39
40 expected = {
40 expected = {
41 'access_id': gist_id,
41 'access_id': gist_id,
42 'created_on': gist_created_on,
42 'created_on': gist_created_on,
43 'modified_at': gist_modified_at,
43 'modified_at': gist_modified_at,
44 'description': 'new-gist',
44 'description': 'new-gist',
45 'expires': -1.0,
45 'expires': -1.0,
46 'gist_id': int(gist_id),
46 'gist_id': int(gist_id),
47 'type': 'public',
47 'type': 'public',
48 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
48 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
49 'acl_level': Gist.ACL_LEVEL_PUBLIC,
49 'acl_level': Gist.ACL_LEVEL_PUBLIC,
50 'content': None,
50 'content': None,
51 }
51 }
52
52
53 assert_ok(id_, expected, given=response.body)
53 assert_ok(id_, expected, given=response.body)
54
54
55 def test_api_get_gist_with_content(self, gist_util, http_host_only_stub):
55 def test_api_get_gist_with_content(self, gist_util, http_host_only_stub):
56 mapping = {
56 mapping = {
57 b'filename1.txt': {'content': b'hello world'},
57 b'filename1.txt': {'content': b'hello world'},
58 safe_bytes('filename1ą.txt'): {'content': safe_bytes('hello worldę')}
58 safe_bytes('filename1ą.txt'): {'content': safe_bytes('hello worldę')}
59 }
59 }
60 gist = gist_util.create_gist(gist_mapping=mapping)
60 gist = gist_util.create_gist(gist_mapping=mapping)
61 gist_id = gist.gist_access_id
61 gist_id = gist.gist_access_id
62 gist_created_on = gist.created_on
62 gist_created_on = gist.created_on
63 gist_modified_at = gist.modified_at
63 gist_modified_at = gist.modified_at
64 id_, params = build_data(
64 id_, params = build_data(
65 self.apikey, 'get_gist', gistid=gist_id, content=True)
65 self.apikey, 'get_gist', gistid=gist_id, content=True)
66 response = api_call(self.app, params)
66 response = api_call(self.app, params)
67
67
68 expected = {
68 expected = {
69 'access_id': gist_id,
69 'access_id': gist_id,
70 'created_on': gist_created_on,
70 'created_on': gist_created_on,
71 'modified_at': gist_modified_at,
71 'modified_at': gist_modified_at,
72 'description': 'new-gist',
72 'description': 'new-gist',
73 'expires': -1.0,
73 'expires': -1.0,
74 'gist_id': int(gist_id),
74 'gist_id': int(gist_id),
75 'type': 'public',
75 'type': 'public',
76 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
76 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
77 'acl_level': Gist.ACL_LEVEL_PUBLIC,
77 'acl_level': Gist.ACL_LEVEL_PUBLIC,
78 'content': {
78 'content': {
79 u'filename1.txt': u'hello world',
79 'filename1.txt': 'hello world',
80 u'filename1ą.txt': u'hello worldę'
80 'filename1ą.txt': 'hello worldę'
81 },
81 },
82 }
82 }
83
83
84 assert_ok(id_, expected, given=response.body)
84 assert_ok(id_, expected, given=response.body)
85
85
86 def test_api_get_gist_not_existing(self):
86 def test_api_get_gist_not_existing(self):
87 id_, params = build_data(
87 id_, params = build_data(
88 self.apikey_regular, 'get_gist', gistid='12345', )
88 self.apikey_regular, 'get_gist', gistid='12345', )
89 response = api_call(self.app, params)
89 response = api_call(self.app, params)
90 expected = 'gist `%s` does not exist' % ('12345',)
90 expected = 'gist `%s` does not exist' % ('12345',)
91 assert_error(id_, expected, given=response.body)
91 assert_error(id_, expected, given=response.body)
92
92
93 def test_api_get_gist_private_gist_without_permission(self, gist_util):
93 def test_api_get_gist_private_gist_without_permission(self, gist_util):
94 gist = gist_util.create_gist()
94 gist = gist_util.create_gist()
95 gist_id = gist.gist_access_id
95 gist_id = gist.gist_access_id
96 id_, params = build_data(
96 id_, params = build_data(
97 self.apikey_regular, 'get_gist', gistid=gist_id, )
97 self.apikey_regular, 'get_gist', gistid=gist_id, )
98 response = api_call(self.app, params)
98 response = api_call(self.app, params)
99
99
100 expected = 'gist `%s` does not exist' % (gist_id,)
100 expected = 'gist `%s` does not exist' % (gist_id,)
101 assert_error(id_, expected, given=response.body)
101 assert_error(id_, expected, given=response.body)
@@ -1,424 +1,423 b''
1 # Copyright (C) 2011-2023 RhodeCode GmbH
1 # Copyright (C) 2011-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import logging
19 import logging
20 import itertools
20 import itertools
21 import base64
21 import base64
22
22
23 from rhodecode.api import (
23 from rhodecode.api import (
24 jsonrpc_method, JSONRPCError, JSONRPCForbidden, find_methods)
24 jsonrpc_method, JSONRPCError, JSONRPCForbidden, find_methods)
25
25
26 from rhodecode.api.utils import (
26 from rhodecode.api.utils import (
27 Optional, OAttr, has_superadmin_permission, get_user_or_error)
27 Optional, OAttr, has_superadmin_permission, get_user_or_error)
28 from rhodecode.lib.utils import repo2db_mapper
28 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_repo_store_path
29 from rhodecode.lib import system_info
29 from rhodecode.lib import system_info
30 from rhodecode.lib import user_sessions
30 from rhodecode.lib import user_sessions
31 from rhodecode.lib import exc_tracking
31 from rhodecode.lib import exc_tracking
32 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.ext_json import json
33 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.lib.utils2 import safe_int
34 from rhodecode.model.db import UserIpMap
34 from rhodecode.model.db import UserIpMap
35 from rhodecode.model.scm import ScmModel
35 from rhodecode.model.scm import ScmModel
36 from rhodecode.model.settings import VcsSettingsModel
37 from rhodecode.apps.file_store import utils
36 from rhodecode.apps.file_store import utils
38 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, \
37 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, \
39 FileOverSizeException
38 FileOverSizeException
40
39
41 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
42
41
43
42
44 @jsonrpc_method()
43 @jsonrpc_method()
45 def get_server_info(request, apiuser):
44 def get_server_info(request, apiuser):
46 """
45 """
47 Returns the |RCE| server information.
46 Returns the |RCE| server information.
48
47
49 This includes the running version of |RCE| and all installed
48 This includes the running version of |RCE| and all installed
50 packages. This command takes the following options:
49 packages. This command takes the following options:
51
50
52 :param apiuser: This is filled automatically from the |authtoken|.
51 :param apiuser: This is filled automatically from the |authtoken|.
53 :type apiuser: AuthUser
52 :type apiuser: AuthUser
54
53
55 Example output:
54 Example output:
56
55
57 .. code-block:: bash
56 .. code-block:: bash
58
57
59 id : <id_given_in_input>
58 id : <id_given_in_input>
60 result : {
59 result : {
61 'modules': [<module name>,...]
60 'modules': [<module name>,...]
62 'py_version': <python version>,
61 'py_version': <python version>,
63 'platform': <platform type>,
62 'platform': <platform type>,
64 'rhodecode_version': <rhodecode version>
63 'rhodecode_version': <rhodecode version>
65 }
64 }
66 error : null
65 error : null
67 """
66 """
68
67
69 if not has_superadmin_permission(apiuser):
68 if not has_superadmin_permission(apiuser):
70 raise JSONRPCForbidden()
69 raise JSONRPCForbidden()
71
70
72 server_info = ScmModel().get_server_info(request.environ)
71 server_info = ScmModel().get_server_info(request.environ)
73 # rhodecode-index requires those
72 # rhodecode-index requires those
74
73
75 server_info['index_storage'] = server_info['search']['value']['location']
74 server_info['index_storage'] = server_info['search']['value']['location']
76 server_info['storage'] = server_info['storage']['value']['path']
75 server_info['storage'] = server_info['storage']['value']['path']
77
76
78 return server_info
77 return server_info
79
78
80
79
81 @jsonrpc_method()
80 @jsonrpc_method()
82 def get_repo_store(request, apiuser):
81 def get_repo_store(request, apiuser):
83 """
82 """
84 Returns the |RCE| repository storage information.
83 Returns the |RCE| repository storage information.
85
84
86 :param apiuser: This is filled automatically from the |authtoken|.
85 :param apiuser: This is filled automatically from the |authtoken|.
87 :type apiuser: AuthUser
86 :type apiuser: AuthUser
88
87
89 Example output:
88 Example output:
90
89
91 .. code-block:: bash
90 .. code-block:: bash
92
91
93 id : <id_given_in_input>
92 id : <id_given_in_input>
94 result : {
93 result : {
95 'modules': [<module name>,...]
94 'modules': [<module name>,...]
96 'py_version': <python version>,
95 'py_version': <python version>,
97 'platform': <platform type>,
96 'platform': <platform type>,
98 'rhodecode_version': <rhodecode version>
97 'rhodecode_version': <rhodecode version>
99 }
98 }
100 error : null
99 error : null
101 """
100 """
102
101
103 if not has_superadmin_permission(apiuser):
102 if not has_superadmin_permission(apiuser):
104 raise JSONRPCForbidden()
103 raise JSONRPCForbidden()
105
104
106 path = VcsSettingsModel().get_repos_location()
105 path = get_rhodecode_repo_store_path()
107 return {"path": path}
106 return {"path": path}
108
107
109
108
110 @jsonrpc_method()
109 @jsonrpc_method()
111 def get_ip(request, apiuser, userid=Optional(OAttr('apiuser'))):
110 def get_ip(request, apiuser, userid=Optional(OAttr('apiuser'))):
112 """
111 """
113 Displays the IP Address as seen from the |RCE| server.
112 Displays the IP Address as seen from the |RCE| server.
114
113
115 * This command displays the IP Address, as well as all the defined IP
114 * This command displays the IP Address, as well as all the defined IP
116 addresses for the specified user. If the ``userid`` is not set, the
115 addresses for the specified user. If the ``userid`` is not set, the
117 data returned is for the user calling the method.
116 data returned is for the user calling the method.
118
117
119 This command can only be run using an |authtoken| with admin rights to
118 This command can only be run using an |authtoken| with admin rights to
120 the specified repository.
119 the specified repository.
121
120
122 This command takes the following options:
121 This command takes the following options:
123
122
124 :param apiuser: This is filled automatically from |authtoken|.
123 :param apiuser: This is filled automatically from |authtoken|.
125 :type apiuser: AuthUser
124 :type apiuser: AuthUser
126 :param userid: Sets the userid for which associated IP Address data
125 :param userid: Sets the userid for which associated IP Address data
127 is returned.
126 is returned.
128 :type userid: Optional(str or int)
127 :type userid: Optional(str or int)
129
128
130 Example output:
129 Example output:
131
130
132 .. code-block:: bash
131 .. code-block:: bash
133
132
134 id : <id_given_in_input>
133 id : <id_given_in_input>
135 result : {
134 result : {
136 "server_ip_addr": "<ip_from_clien>",
135 "server_ip_addr": "<ip_from_clien>",
137 "user_ips": [
136 "user_ips": [
138 {
137 {
139 "ip_addr": "<ip_with_mask>",
138 "ip_addr": "<ip_with_mask>",
140 "ip_range": ["<start_ip>", "<end_ip>"],
139 "ip_range": ["<start_ip>", "<end_ip>"],
141 },
140 },
142 ...
141 ...
143 ]
142 ]
144 }
143 }
145
144
146 """
145 """
147 if not has_superadmin_permission(apiuser):
146 if not has_superadmin_permission(apiuser):
148 raise JSONRPCForbidden()
147 raise JSONRPCForbidden()
149
148
150 userid = Optional.extract(userid, evaluate_locals=locals())
149 userid = Optional.extract(userid, evaluate_locals=locals())
151 userid = getattr(userid, 'user_id', userid)
150 userid = getattr(userid, 'user_id', userid)
152
151
153 user = get_user_or_error(userid)
152 user = get_user_or_error(userid)
154 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
153 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
155 return {
154 return {
156 'server_ip_addr': request.rpc_ip_addr,
155 'server_ip_addr': request.rpc_ip_addr,
157 'user_ips': ips
156 'user_ips': ips
158 }
157 }
159
158
160
159
161 @jsonrpc_method()
160 @jsonrpc_method()
162 def rescan_repos(request, apiuser, remove_obsolete=Optional(False)):
161 def rescan_repos(request, apiuser, remove_obsolete=Optional(False)):
163 """
162 """
164 Triggers a rescan of the specified repositories.
163 Triggers a rescan of the specified repositories.
165
164
166 * If the ``remove_obsolete`` option is set, it also deletes repositories
165 * If the ``remove_obsolete`` option is set, it also deletes repositories
167 that are found in the database but not on the file system, so called
166 that are found in the database but not on the file system, so called
168 "clean zombies".
167 "clean zombies".
169
168
170 This command can only be run using an |authtoken| with admin rights to
169 This command can only be run using an |authtoken| with admin rights to
171 the specified repository.
170 the specified repository.
172
171
173 This command takes the following options:
172 This command takes the following options:
174
173
175 :param apiuser: This is filled automatically from the |authtoken|.
174 :param apiuser: This is filled automatically from the |authtoken|.
176 :type apiuser: AuthUser
175 :type apiuser: AuthUser
177 :param remove_obsolete: Deletes repositories from the database that
176 :param remove_obsolete: Deletes repositories from the database that
178 are not found on the filesystem.
177 are not found on the filesystem.
179 :type remove_obsolete: Optional(``True`` | ``False``)
178 :type remove_obsolete: Optional(``True`` | ``False``)
180
179
181 Example output:
180 Example output:
182
181
183 .. code-block:: bash
182 .. code-block:: bash
184
183
185 id : <id_given_in_input>
184 id : <id_given_in_input>
186 result : {
185 result : {
187 'added': [<added repository name>,...]
186 'added': [<added repository name>,...]
188 'removed': [<removed repository name>,...]
187 'removed': [<removed repository name>,...]
189 }
188 }
190 error : null
189 error : null
191
190
192 Example error output:
191 Example error output:
193
192
194 .. code-block:: bash
193 .. code-block:: bash
195
194
196 id : <id_given_in_input>
195 id : <id_given_in_input>
197 result : null
196 result : null
198 error : {
197 error : {
199 'Error occurred during rescan repositories action'
198 'Error occurred during rescan repositories action'
200 }
199 }
201
200
202 """
201 """
203 if not has_superadmin_permission(apiuser):
202 if not has_superadmin_permission(apiuser):
204 raise JSONRPCForbidden()
203 raise JSONRPCForbidden()
205
204
206 try:
205 try:
207 rm_obsolete = Optional.extract(remove_obsolete)
206 rm_obsolete = Optional.extract(remove_obsolete)
208 added, removed = repo2db_mapper(ScmModel().repo_scan(),
207 added, removed = repo2db_mapper(ScmModel().repo_scan(),
209 remove_obsolete=rm_obsolete, force_hooks_rebuild=True)
208 remove_obsolete=rm_obsolete, force_hooks_rebuild=True)
210 return {'added': added, 'removed': removed}
209 return {'added': added, 'removed': removed}
211 except Exception:
210 except Exception:
212 log.exception('Failed to run repo rescann')
211 log.exception('Failed to run repo rescann')
213 raise JSONRPCError(
212 raise JSONRPCError(
214 'Error occurred during rescan repositories action'
213 'Error occurred during rescan repositories action'
215 )
214 )
216
215
217
216
218 @jsonrpc_method()
217 @jsonrpc_method()
219 def cleanup_sessions(request, apiuser, older_then=Optional(60)):
218 def cleanup_sessions(request, apiuser, older_then=Optional(60)):
220 """
219 """
221 Triggers a session cleanup action.
220 Triggers a session cleanup action.
222
221
223 If the ``older_then`` option is set, only sessions that hasn't been
222 If the ``older_then`` option is set, only sessions that hasn't been
224 accessed in the given number of days will be removed.
223 accessed in the given number of days will be removed.
225
224
226 This command can only be run using an |authtoken| with admin rights to
225 This command can only be run using an |authtoken| with admin rights to
227 the specified repository.
226 the specified repository.
228
227
229 This command takes the following options:
228 This command takes the following options:
230
229
231 :param apiuser: This is filled automatically from the |authtoken|.
230 :param apiuser: This is filled automatically from the |authtoken|.
232 :type apiuser: AuthUser
231 :type apiuser: AuthUser
233 :param older_then: Deletes session that hasn't been accessed
232 :param older_then: Deletes session that hasn't been accessed
234 in given number of days.
233 in given number of days.
235 :type older_then: Optional(int)
234 :type older_then: Optional(int)
236
235
237 Example output:
236 Example output:
238
237
239 .. code-block:: bash
238 .. code-block:: bash
240
239
241 id : <id_given_in_input>
240 id : <id_given_in_input>
242 result: {
241 result: {
243 "backend": "<type of backend>",
242 "backend": "<type of backend>",
244 "sessions_removed": <number_of_removed_sessions>
243 "sessions_removed": <number_of_removed_sessions>
245 }
244 }
246 error : null
245 error : null
247
246
248 Example error output:
247 Example error output:
249
248
250 .. code-block:: bash
249 .. code-block:: bash
251
250
252 id : <id_given_in_input>
251 id : <id_given_in_input>
253 result : null
252 result : null
254 error : {
253 error : {
255 'Error occurred during session cleanup'
254 'Error occurred during session cleanup'
256 }
255 }
257
256
258 """
257 """
259 if not has_superadmin_permission(apiuser):
258 if not has_superadmin_permission(apiuser):
260 raise JSONRPCForbidden()
259 raise JSONRPCForbidden()
261
260
262 older_then = safe_int(Optional.extract(older_then)) or 60
261 older_then = safe_int(Optional.extract(older_then)) or 60
263 older_than_seconds = 60 * 60 * 24 * older_then
262 older_than_seconds = 60 * 60 * 24 * older_then
264
263
265 config = system_info.rhodecode_config().get_value()['value']['config']
264 config = system_info.rhodecode_config().get_value()['value']['config']
266 session_model = user_sessions.get_session_handler(
265 session_model = user_sessions.get_session_handler(
267 config.get('beaker.session.type', 'memory'))(config)
266 config.get('beaker.session.type', 'memory'))(config)
268
267
269 backend = session_model.SESSION_TYPE
268 backend = session_model.SESSION_TYPE
270 try:
269 try:
271 cleaned = session_model.clean_sessions(
270 cleaned = session_model.clean_sessions(
272 older_than_seconds=older_than_seconds)
271 older_than_seconds=older_than_seconds)
273 return {'sessions_removed': cleaned, 'backend': backend}
272 return {'sessions_removed': cleaned, 'backend': backend}
274 except user_sessions.CleanupCommand as msg:
273 except user_sessions.CleanupCommand as msg:
275 return {'cleanup_command': str(msg), 'backend': backend}
274 return {'cleanup_command': str(msg), 'backend': backend}
276 except Exception as e:
275 except Exception as e:
277 log.exception('Failed session cleanup')
276 log.exception('Failed session cleanup')
278 raise JSONRPCError(
277 raise JSONRPCError(
279 'Error occurred during session cleanup'
278 'Error occurred during session cleanup'
280 )
279 )
281
280
282
281
283 @jsonrpc_method()
282 @jsonrpc_method()
284 def get_method(request, apiuser, pattern=Optional('*')):
283 def get_method(request, apiuser, pattern=Optional('*')):
285 """
284 """
286 Returns list of all available API methods. By default match pattern
285 Returns list of all available API methods. By default match pattern
287 os "*" but any other pattern can be specified. eg *comment* will return
286 os "*" but any other pattern can be specified. eg *comment* will return
288 all methods with comment inside them. If just single method is matched
287 all methods with comment inside them. If just single method is matched
289 returned data will also include method specification
288 returned data will also include method specification
290
289
291 This command can only be run using an |authtoken| with admin rights to
290 This command can only be run using an |authtoken| with admin rights to
292 the specified repository.
291 the specified repository.
293
292
294 This command takes the following options:
293 This command takes the following options:
295
294
296 :param apiuser: This is filled automatically from the |authtoken|.
295 :param apiuser: This is filled automatically from the |authtoken|.
297 :type apiuser: AuthUser
296 :type apiuser: AuthUser
298 :param pattern: pattern to match method names against
297 :param pattern: pattern to match method names against
299 :type pattern: Optional("*")
298 :type pattern: Optional("*")
300
299
301 Example output:
300 Example output:
302
301
303 .. code-block:: bash
302 .. code-block:: bash
304
303
305 id : <id_given_in_input>
304 id : <id_given_in_input>
306 "result": [
305 "result": [
307 "changeset_comment",
306 "changeset_comment",
308 "comment_pull_request",
307 "comment_pull_request",
309 "comment_commit"
308 "comment_commit"
310 ]
309 ]
311 error : null
310 error : null
312
311
313 .. code-block:: bash
312 .. code-block:: bash
314
313
315 id : <id_given_in_input>
314 id : <id_given_in_input>
316 "result": [
315 "result": [
317 "comment_commit",
316 "comment_commit",
318 {
317 {
319 "apiuser": "<RequiredType>",
318 "apiuser": "<RequiredType>",
320 "comment_type": "<Optional:u'note'>",
319 "comment_type": "<Optional:u'note'>",
321 "commit_id": "<RequiredType>",
320 "commit_id": "<RequiredType>",
322 "message": "<RequiredType>",
321 "message": "<RequiredType>",
323 "repoid": "<RequiredType>",
322 "repoid": "<RequiredType>",
324 "request": "<RequiredType>",
323 "request": "<RequiredType>",
325 "resolves_comment_id": "<Optional:None>",
324 "resolves_comment_id": "<Optional:None>",
326 "status": "<Optional:None>",
325 "status": "<Optional:None>",
327 "userid": "<Optional:<OptionalAttr:apiuser>>"
326 "userid": "<Optional:<OptionalAttr:apiuser>>"
328 }
327 }
329 ]
328 ]
330 error : null
329 error : null
331 """
330 """
332 from rhodecode.config.patches import inspect_getargspec
331 from rhodecode.config.patches import inspect_getargspec
333 inspect = inspect_getargspec()
332 inspect = inspect_getargspec()
334
333
335 if not has_superadmin_permission(apiuser):
334 if not has_superadmin_permission(apiuser):
336 raise JSONRPCForbidden()
335 raise JSONRPCForbidden()
337
336
338 pattern = Optional.extract(pattern)
337 pattern = Optional.extract(pattern)
339
338
340 matches = find_methods(request.registry.jsonrpc_methods, pattern)
339 matches = find_methods(request.registry.jsonrpc_methods, pattern)
341
340
342 args_desc = []
341 args_desc = []
343 matches_keys = list(matches.keys())
342 matches_keys = list(matches.keys())
344 if len(matches_keys) == 1:
343 if len(matches_keys) == 1:
345 func = matches[matches_keys[0]]
344 func = matches[matches_keys[0]]
346
345
347 argspec = inspect.getargspec(func)
346 argspec = inspect.getargspec(func)
348 arglist = argspec[0]
347 arglist = argspec[0]
349 defaults = list(map(repr, argspec[3] or []))
348 defaults = list(map(repr, argspec[3] or []))
350
349
351 default_empty = '<RequiredType>'
350 default_empty = '<RequiredType>'
352
351
353 # kw arguments required by this method
352 # kw arguments required by this method
354 func_kwargs = dict(itertools.zip_longest(
353 func_kwargs = dict(itertools.zip_longest(
355 reversed(arglist), reversed(defaults), fillvalue=default_empty))
354 reversed(arglist), reversed(defaults), fillvalue=default_empty))
356 args_desc.append(func_kwargs)
355 args_desc.append(func_kwargs)
357
356
358 return matches_keys + args_desc
357 return matches_keys + args_desc
359
358
360
359
361 @jsonrpc_method()
360 @jsonrpc_method()
362 def store_exception(request, apiuser, exc_data_json, prefix=Optional('rhodecode')):
361 def store_exception(request, apiuser, exc_data_json, prefix=Optional('rhodecode')):
363 """
362 """
364 Stores sent exception inside the built-in exception tracker in |RCE| server.
363 Stores sent exception inside the built-in exception tracker in |RCE| server.
365
364
366 This command can only be run using an |authtoken| with admin rights to
365 This command can only be run using an |authtoken| with admin rights to
367 the specified repository.
366 the specified repository.
368
367
369 This command takes the following options:
368 This command takes the following options:
370
369
371 :param apiuser: This is filled automatically from the |authtoken|.
370 :param apiuser: This is filled automatically from the |authtoken|.
372 :type apiuser: AuthUser
371 :type apiuser: AuthUser
373
372
374 :param exc_data_json: JSON data with exception e.g
373 :param exc_data_json: JSON data with exception e.g
375 {"exc_traceback": "Value `1` is not allowed", "exc_type_name": "ValueError"}
374 {"exc_traceback": "Value `1` is not allowed", "exc_type_name": "ValueError"}
376 :type exc_data_json: JSON data
375 :type exc_data_json: JSON data
377
376
378 :param prefix: prefix for error type, e.g 'rhodecode', 'vcsserver', 'rhodecode-tools'
377 :param prefix: prefix for error type, e.g 'rhodecode', 'vcsserver', 'rhodecode-tools'
379 :type prefix: Optional("rhodecode")
378 :type prefix: Optional("rhodecode")
380
379
381 Example output:
380 Example output:
382
381
383 .. code-block:: bash
382 .. code-block:: bash
384
383
385 id : <id_given_in_input>
384 id : <id_given_in_input>
386 "result": {
385 "result": {
387 "exc_id": 139718459226384,
386 "exc_id": 139718459226384,
388 "exc_url": "http://localhost:8080/_admin/settings/exceptions/139718459226384"
387 "exc_url": "http://localhost:8080/_admin/settings/exceptions/139718459226384"
389 }
388 }
390 error : null
389 error : null
391 """
390 """
392 if not has_superadmin_permission(apiuser):
391 if not has_superadmin_permission(apiuser):
393 raise JSONRPCForbidden()
392 raise JSONRPCForbidden()
394
393
395 prefix = Optional.extract(prefix)
394 prefix = Optional.extract(prefix)
396 exc_id = exc_tracking.generate_id()
395 exc_id = exc_tracking.generate_id()
397
396
398 try:
397 try:
399 exc_data = json.loads(exc_data_json)
398 exc_data = json.loads(exc_data_json)
400 except Exception:
399 except Exception:
401 log.error('Failed to parse JSON: %r', exc_data_json)
400 log.error('Failed to parse JSON: %r', exc_data_json)
402 raise JSONRPCError('Failed to parse JSON data from exc_data_json field. '
401 raise JSONRPCError('Failed to parse JSON data from exc_data_json field. '
403 'Please make sure it contains a valid JSON.')
402 'Please make sure it contains a valid JSON.')
404
403
405 try:
404 try:
406 exc_traceback = exc_data['exc_traceback']
405 exc_traceback = exc_data['exc_traceback']
407 exc_type_name = exc_data['exc_type_name']
406 exc_type_name = exc_data['exc_type_name']
408 exc_value = ''
407 exc_value = ''
409 except KeyError as err:
408 except KeyError as err:
410 raise JSONRPCError(
409 raise JSONRPCError(
411 f'Missing exc_traceback, or exc_type_name '
410 f'Missing exc_traceback, or exc_type_name '
412 f'in exc_data_json field. Missing: {err}')
411 f'in exc_data_json field. Missing: {err}')
413
412
414 class ExcType:
413 class ExcType:
415 __name__ = exc_type_name
414 __name__ = exc_type_name
416
415
417 exc_info = (ExcType(), exc_value, exc_traceback)
416 exc_info = (ExcType(), exc_value, exc_traceback)
418
417
419 exc_tracking._store_exception(
418 exc_tracking._store_exception(
420 exc_id=exc_id, exc_info=exc_info, prefix=prefix)
419 exc_id=exc_id, exc_info=exc_info, prefix=prefix)
421
420
422 exc_url = request.route_url(
421 exc_url = request.route_url(
423 'admin_settings_exception_tracker_show', exception_id=exc_id)
422 'admin_settings_exception_tracker_show', exception_id=exc_id)
424 return {'exc_id': exc_id, 'exc_url': exc_url}
423 return {'exc_id': exc_id, 'exc_url': exc_url}
@@ -1,947 +1,987 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import time
19 import time
20 import logging
20 import logging
21 import operator
21 import operator
22
22
23 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
23 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
24
24
25 from rhodecode.lib import helpers as h, diffs, rc_cache
25 from rhodecode.lib import helpers as h, diffs, rc_cache
26 from rhodecode.lib.str_utils import safe_str
26 from rhodecode.lib.str_utils import safe_str
27 from rhodecode.lib.utils import repo_name_slug
27 from rhodecode.lib.utils import repo_name_slug
28 from rhodecode.lib.utils2 import (
28 from rhodecode.lib.utils2 import (
29 StrictAttributeDict,
29 StrictAttributeDict,
30 str2bool,
30 str2bool,
31 safe_int,
31 safe_int,
32 datetime_to_time,
32 datetime_to_time,
33 )
33 )
34 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
34 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
35 from rhodecode.lib.vcs.backends.base import EmptyCommit
35 from rhodecode.lib.vcs.backends.base import EmptyCommit
36 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
36 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
37 from rhodecode.model import repo
37 from rhodecode.model import repo
38 from rhodecode.model import repo_group
38 from rhodecode.model import repo_group
39 from rhodecode.model import user_group
39 from rhodecode.model import user_group
40 from rhodecode.model import user
40 from rhodecode.model import user
41 from rhodecode.model.db import User
41 from rhodecode.model.db import User
42 from rhodecode.model.scm import ScmModel
42 from rhodecode.model.scm import ScmModel
43 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
43 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
44 from rhodecode.model.repo import ReadmeFinder
44 from rhodecode.model.repo import ReadmeFinder
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 ADMIN_PREFIX: str = "/_admin"
49 ADMIN_PREFIX: str = "/_admin"
50 STATIC_FILE_PREFIX: str = "/_static"
50 STATIC_FILE_PREFIX: str = "/_static"
51
51
52 URL_NAME_REQUIREMENTS = {
52 URL_NAME_REQUIREMENTS = {
53 # group name can have a slash in them, but they must not end with a slash
53 # group name can have a slash in them, but they must not end with a slash
54 "group_name": r".*?[^/]",
54 "group_name": r".*?[^/]",
55 "repo_group_name": r".*?[^/]",
55 "repo_group_name": r".*?[^/]",
56 # repo names can have a slash in them, but they must not end with a slash
56 # repo names can have a slash in them, but they must not end with a slash
57 "repo_name": r".*?[^/]",
57 "repo_name": r".*?[^/]",
58 # file path eats up everything at the end
58 # file path eats up everything at the end
59 "f_path": r".*",
59 "f_path": r".*",
60 # reference types
60 # reference types
61 "source_ref_type": r"(branch|book|tag|rev|\%\(source_ref_type\)s)",
61 "source_ref_type": r"(branch|book|tag|rev|\%\(source_ref_type\)s)",
62 "target_ref_type": r"(branch|book|tag|rev|\%\(target_ref_type\)s)",
62 "target_ref_type": r"(branch|book|tag|rev|\%\(target_ref_type\)s)",
63 }
63 }
64
64
65
65
66 def add_route_with_slash(config, name, pattern, **kw):
66 def add_route_with_slash(config, name, pattern, **kw):
67 config.add_route(name, pattern, **kw)
67 config.add_route(name, pattern, **kw)
68 if not pattern.endswith("/"):
68 if not pattern.endswith("/"):
69 config.add_route(name + "_slash", pattern + "/", **kw)
69 config.add_route(name + "_slash", pattern + "/", **kw)
70
70
71
71
72 def add_route_requirements(route_path, requirements=None):
72 def add_route_requirements(route_path, requirements=None):
73 """
73 """
74 Adds regex requirements to pyramid routes using a mapping dict
74 Adds regex requirements to pyramid routes using a mapping dict
75 e.g::
75 e.g::
76 add_route_requirements('{repo_name}/settings')
76 add_route_requirements('{repo_name}/settings')
77 """
77 """
78 requirements = requirements or URL_NAME_REQUIREMENTS
78 requirements = requirements or URL_NAME_REQUIREMENTS
79 for key, regex in list(requirements.items()):
79 for key, regex in list(requirements.items()):
80 route_path = route_path.replace("{%s}" % key, "{%s:%s}" % (key, regex))
80 route_path = route_path.replace("{%s}" % key, "{%s:%s}" % (key, regex))
81 return route_path
81 return route_path
82
82
83
83
84 def get_format_ref_id(repo):
84 def get_format_ref_id(repo):
85 """Returns a `repo` specific reference formatter function"""
85 """Returns a `repo` specific reference formatter function"""
86 if h.is_svn(repo):
86 if h.is_svn(repo):
87 return _format_ref_id_svn
87 return _format_ref_id_svn
88 else:
88 else:
89 return _format_ref_id
89 return _format_ref_id
90
90
91
91
92 def _format_ref_id(name, raw_id):
92 def _format_ref_id(name, raw_id):
93 """Default formatting of a given reference `name`"""
93 """Default formatting of a given reference `name`"""
94 return name
94 return name
95
95
96
96
97 def _format_ref_id_svn(name, raw_id):
97 def _format_ref_id_svn(name, raw_id):
98 """Special way of formatting a reference for Subversion including path"""
98 """Special way of formatting a reference for Subversion including path"""
99 return f"{name}@{raw_id}"
99 return f"{name}@{raw_id}"
100
100
101
101
102 class TemplateArgs(StrictAttributeDict):
102 class TemplateArgs(StrictAttributeDict):
103 pass
103 pass
104
104
105
105
106 class BaseAppView(object):
106 class BaseAppView(object):
107 DONT_CHECKOUT_VIEWS = ["channelstream_connect", "ops_ping"]
108 EXTRA_VIEWS_TO_IGNORE = ['login', 'register', 'logout']
109 SETUP_2FA_VIEW = 'setup_2fa'
110 VERIFY_2FA_VIEW = 'check_2fa'
111
107 def __init__(self, context, request):
112 def __init__(self, context, request):
108 self.request = request
113 self.request = request
109 self.context = context
114 self.context = context
110 self.session = request.session
115 self.session = request.session
111 if not hasattr(request, "user"):
116 if not hasattr(request, "user"):
112 # NOTE(marcink): edge case, we ended up in matched route
117 # NOTE(marcink): edge case, we ended up in matched route
113 # but probably of web-app context, e.g API CALL/VCS CALL
118 # but probably of web-app context, e.g API CALL/VCS CALL
114 if hasattr(request, "vcs_call") or hasattr(request, "rpc_method"):
119 if hasattr(request, "vcs_call") or hasattr(request, "rpc_method"):
115 log.warning("Unable to process request `%s` in this scope", request)
120 log.warning("Unable to process request `%s` in this scope", request)
116 raise HTTPBadRequest()
121 raise HTTPBadRequest()
117
122
118 self._rhodecode_user = request.user # auth user
123 self._rhodecode_user = request.user # auth user
119 self._rhodecode_db_user = self._rhodecode_user.get_instance()
124 self._rhodecode_db_user = self._rhodecode_user.get_instance()
125 self.user_data = self._rhodecode_db_user.user_data if self._rhodecode_db_user else {}
120 self._maybe_needs_password_change(
126 self._maybe_needs_password_change(
121 request.matched_route.name, self._rhodecode_db_user
127 request.matched_route.name, self._rhodecode_db_user
122 )
128 )
129 self._maybe_needs_2fa_configuration(
130 request.matched_route.name, self._rhodecode_db_user
131 )
132 self._maybe_needs_2fa_check(
133 request.matched_route.name, self._rhodecode_db_user
134 )
123
135
124 def _maybe_needs_password_change(self, view_name, user_obj):
136 def _maybe_needs_password_change(self, view_name, user_obj):
125 dont_check_views = ["channelstream_connect", "ops_ping"]
137 if view_name in self.DONT_CHECKOUT_VIEWS:
126 if view_name in dont_check_views:
127 return
138 return
128
139
129 log.debug(
140 log.debug(
130 "Checking if user %s needs password change on view %s", user_obj, view_name
141 "Checking if user %s needs password change on view %s", user_obj, view_name
131 )
142 )
132
143
133 skip_user_views = [
144 skip_user_views = [
134 "logout",
145 "logout",
135 "login",
146 "login",
147 "check_2fa",
136 "my_account_password",
148 "my_account_password",
137 "my_account_password_update",
149 "my_account_password_update",
138 ]
150 ]
139
151
140 if not user_obj:
152 if not user_obj:
141 return
153 return
142
154
143 if user_obj.username == User.DEFAULT_USER:
155 if user_obj.username == User.DEFAULT_USER:
144 return
156 return
145
157
146 now = time.time()
158 now = time.time()
147 should_change = user_obj.user_data.get("force_password_change")
159 should_change = self.user_data.get("force_password_change")
148 change_after = safe_int(should_change) or 0
160 change_after = safe_int(should_change) or 0
149 if should_change and now > change_after:
161 if should_change and now > change_after:
150 log.debug("User %s requires password change", user_obj)
162 log.debug("User %s requires password change", user_obj)
151 h.flash(
163 h.flash(
152 "You are required to change your password",
164 "You are required to change your password",
153 "warning",
165 "warning",
154 ignore_duplicate=True,
166 ignore_duplicate=True,
155 )
167 )
156
168
157 if view_name not in skip_user_views:
169 if view_name not in skip_user_views:
158 raise HTTPFound(self.request.route_path("my_account_password"))
170 raise HTTPFound(self.request.route_path("my_account_password"))
159
171
172 def _maybe_needs_2fa_configuration(self, view_name, user_obj):
173 if view_name in self.DONT_CHECKOUT_VIEWS + self.EXTRA_VIEWS_TO_IGNORE:
174 return
175
176 if not user_obj:
177 return
178
179 if user_obj.needs_2fa_configure and view_name != self.SETUP_2FA_VIEW:
180 h.flash(
181 "You are required to configure 2FA",
182 "warning",
183 ignore_duplicate=False,
184 )
185 # Special case for users created "on the fly" (ldap case for new user)
186 user_obj.check_2fa_required = False
187 raise HTTPFound(self.request.route_path(self.SETUP_2FA_VIEW))
188
189 def _maybe_needs_2fa_check(self, view_name, user_obj):
190 if view_name in self.DONT_CHECKOUT_VIEWS + self.EXTRA_VIEWS_TO_IGNORE:
191 return
192
193 if not user_obj:
194 return
195
196 if user_obj.check_2fa_required and view_name != self.VERIFY_2FA_VIEW:
197 raise HTTPFound(self.request.route_path(self.VERIFY_2FA_VIEW))
198
160 def _log_creation_exception(self, e, repo_name):
199 def _log_creation_exception(self, e, repo_name):
161 _ = self.request.translate
200 _ = self.request.translate
162 reason = None
201 reason = None
163 if len(e.args) == 2:
202 if len(e.args) == 2:
164 reason = e.args[1]
203 reason = e.args[1]
165
204
166 if reason == "INVALID_CERTIFICATE":
205 if reason == "INVALID_CERTIFICATE":
167 log.exception("Exception creating a repository: invalid certificate")
206 log.exception("Exception creating a repository: invalid certificate")
168 msg = _("Error creating repository %s: invalid certificate") % repo_name
207 msg = _("Error creating repository %s: invalid certificate") % repo_name
169 else:
208 else:
170 log.exception("Exception creating a repository")
209 log.exception("Exception creating a repository")
171 msg = _("Error creating repository %s") % repo_name
210 msg = _("Error creating repository %s") % repo_name
172 return msg
211 return msg
173
212
174 def _get_local_tmpl_context(self, include_app_defaults=True):
213 def _get_local_tmpl_context(self, include_app_defaults=True):
175 c = TemplateArgs()
214 c = TemplateArgs()
176 c.auth_user = self.request.user
215 c.auth_user = self.request.user
177 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
216 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
178 c.rhodecode_user = self.request.user
217 c.rhodecode_user = self.request.user
179
218
180 if include_app_defaults:
219 if include_app_defaults:
181 from rhodecode.lib.base import attach_context_attributes
220 from rhodecode.lib.base import attach_context_attributes
182
221
183 attach_context_attributes(c, self.request, self.request.user.user_id)
222 attach_context_attributes(c, self.request, self.request.user.user_id)
184
223
185 c.is_super_admin = c.auth_user.is_admin
224 c.is_super_admin = c.auth_user.is_admin
186
225
187 c.can_create_repo = c.is_super_admin
226 c.can_create_repo = c.is_super_admin
188 c.can_create_repo_group = c.is_super_admin
227 c.can_create_repo_group = c.is_super_admin
189 c.can_create_user_group = c.is_super_admin
228 c.can_create_user_group = c.is_super_admin
190
229
191 c.is_delegated_admin = False
230 c.is_delegated_admin = False
192
231
193 if not c.auth_user.is_default and not c.is_super_admin:
232 if not c.auth_user.is_default and not c.is_super_admin:
194 c.can_create_repo = h.HasPermissionAny("hg.create.repository")(
233 c.can_create_repo = h.HasPermissionAny("hg.create.repository")(
195 user=self.request.user
234 user=self.request.user
196 )
235 )
197 repositories = c.auth_user.repositories_admin or c.can_create_repo
236 repositories = c.auth_user.repositories_admin or c.can_create_repo
198
237
199 c.can_create_repo_group = h.HasPermissionAny("hg.repogroup.create.true")(
238 c.can_create_repo_group = h.HasPermissionAny("hg.repogroup.create.true")(
200 user=self.request.user
239 user=self.request.user
201 )
240 )
202 repository_groups = (
241 repository_groups = (
203 c.auth_user.repository_groups_admin or c.can_create_repo_group
242 c.auth_user.repository_groups_admin or c.can_create_repo_group
204 )
243 )
205
244
206 c.can_create_user_group = h.HasPermissionAny("hg.usergroup.create.true")(
245 c.can_create_user_group = h.HasPermissionAny("hg.usergroup.create.true")(
207 user=self.request.user
246 user=self.request.user
208 )
247 )
209 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
248 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
210 # delegated admin can create, or manage some objects
249 # delegated admin can create, or manage some objects
211 c.is_delegated_admin = repositories or repository_groups or user_groups
250 c.is_delegated_admin = repositories or repository_groups or user_groups
212 return c
251 return c
213
252
214 def _get_template_context(self, tmpl_args, **kwargs):
253 def _get_template_context(self, tmpl_args, **kwargs):
215 local_tmpl_args = {"defaults": {}, "errors": {}, "c": tmpl_args}
254 local_tmpl_args = {"defaults": {}, "errors": {}, "c": tmpl_args}
216 local_tmpl_args.update(kwargs)
255 local_tmpl_args.update(kwargs)
217 return local_tmpl_args
256 return local_tmpl_args
218
257
219 def load_default_context(self):
258 def load_default_context(self):
220 """
259 """
221 example:
260 example:
222
261
223 def load_default_context(self):
262 def load_default_context(self):
224 c = self._get_local_tmpl_context()
263 c = self._get_local_tmpl_context()
225 c.custom_var = 'foobar'
264 c.custom_var = 'foobar'
226
265
227 return c
266 return c
228 """
267 """
229 raise NotImplementedError("Needs implementation in view class")
268 raise NotImplementedError("Needs implementation in view class")
230
269
231
270
232 class RepoAppView(BaseAppView):
271 class RepoAppView(BaseAppView):
233 def __init__(self, context, request):
272 def __init__(self, context, request):
234 super().__init__(context, request)
273 super().__init__(context, request)
235 self.db_repo = request.db_repo
274 self.db_repo = request.db_repo
236 self.db_repo_name = self.db_repo.repo_name
275 self.db_repo_name = self.db_repo.repo_name
237 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
276 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
238 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
277 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
239 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
278 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
240
279
241 def _handle_missing_requirements(self, error):
280 def _handle_missing_requirements(self, error):
242 log.error(
281 log.error(
243 "Requirements are missing for repository %s: %s",
282 "Requirements are missing for repository %s: %s",
244 self.db_repo_name,
283 self.db_repo_name,
245 safe_str(error),
284 safe_str(error),
246 )
285 )
247
286
248 def _prepare_and_set_clone_url(self, c):
287 def _prepare_and_set_clone_url(self, c):
249 username = ""
288 username = ""
250 if self._rhodecode_user.username != User.DEFAULT_USER:
289 if self._rhodecode_user.username != User.DEFAULT_USER:
251 username = self._rhodecode_user.username
290 username = self._rhodecode_user.username
252
291
253 _def_clone_uri = c.clone_uri_tmpl
292 _def_clone_uri = c.clone_uri_tmpl
254 _def_clone_uri_id = c.clone_uri_id_tmpl
293 _def_clone_uri_id = c.clone_uri_id_tmpl
255 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
294 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
256
295
257 c.clone_repo_url = self.db_repo.clone_url(
296 c.clone_repo_url = self.db_repo.clone_url(
258 user=username, uri_tmpl=_def_clone_uri
297 user=username, uri_tmpl=_def_clone_uri
259 )
298 )
260 c.clone_repo_url_id = self.db_repo.clone_url(
299 c.clone_repo_url_id = self.db_repo.clone_url(
261 user=username, uri_tmpl=_def_clone_uri_id
300 user=username, uri_tmpl=_def_clone_uri_id
262 )
301 )
263 c.clone_repo_url_ssh = self.db_repo.clone_url(
302 c.clone_repo_url_ssh = self.db_repo.clone_url(
264 uri_tmpl=_def_clone_uri_ssh, ssh=True
303 uri_tmpl=_def_clone_uri_ssh, ssh=True
265 )
304 )
266
305
267 def _get_local_tmpl_context(self, include_app_defaults=True):
306 def _get_local_tmpl_context(self, include_app_defaults=True):
268 _ = self.request.translate
307 _ = self.request.translate
269 c = super()._get_local_tmpl_context(include_app_defaults=include_app_defaults)
308 c = super()._get_local_tmpl_context(include_app_defaults=include_app_defaults)
270
309
271 # register common vars for this type of view
310 # register common vars for this type of view
272 c.rhodecode_db_repo = self.db_repo
311 c.rhodecode_db_repo = self.db_repo
273 c.repo_name = self.db_repo_name
312 c.repo_name = self.db_repo_name
274 c.repository_pull_requests = self.db_repo_pull_requests
313 c.repository_pull_requests = self.db_repo_pull_requests
275 c.repository_artifacts = self.db_repo_artifacts
314 c.repository_artifacts = self.db_repo_artifacts
276 c.repository_is_user_following = ScmModel().is_following_repo(
315 c.repository_is_user_following = ScmModel().is_following_repo(
277 self.db_repo_name, self._rhodecode_user.user_id
316 self.db_repo_name, self._rhodecode_user.user_id
278 )
317 )
279 self.path_filter = PathFilter(None)
318 self.path_filter = PathFilter(None)
280
319
281 c.repository_requirements_missing = {}
320 c.repository_requirements_missing = {}
282 try:
321 try:
283 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
322 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
284 # NOTE(marcink):
323 # NOTE(marcink):
285 # comparison to None since if it's an object __bool__ is expensive to
324 # comparison to None since if it's an object __bool__ is expensive to
286 # calculate
325 # calculate
287 if self.rhodecode_vcs_repo is not None:
326 if self.rhodecode_vcs_repo is not None:
288 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
327 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
289 c.auth_user.username
328 c.auth_user.username
290 )
329 )
291 self.path_filter = PathFilter(path_perms)
330 self.path_filter = PathFilter(path_perms)
292 except RepositoryRequirementError as e:
331 except RepositoryRequirementError as e:
293 c.repository_requirements_missing = {"error": str(e)}
332 c.repository_requirements_missing = {"error": str(e)}
294 self._handle_missing_requirements(e)
333 self._handle_missing_requirements(e)
295 self.rhodecode_vcs_repo = None
334 self.rhodecode_vcs_repo = None
296
335
297 c.path_filter = self.path_filter # used by atom_feed_entry.mako
336 c.path_filter = self.path_filter # used by atom_feed_entry.mako
298
337
299 if self.rhodecode_vcs_repo is None:
338 if self.rhodecode_vcs_repo is None:
300 # unable to fetch this repo as vcs instance, report back to user
339 # unable to fetch this repo as vcs instance, report back to user
301 log.debug(
340 log.debug(
302 "Repository was not found on filesystem, check if it exists or is not damaged"
341 "Repository was not found on filesystem, check if it exists or is not damaged"
303 )
342 )
304 h.flash(
343 h.flash(
305 _(
344 _(
306 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
345 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
307 "Please check if it exist, or is not damaged."
346 "Please check if it exist, or is not damaged."
308 )
347 )
309 % {"repo_name": c.repo_name},
348 % {"repo_name": c.repo_name},
310 category="error",
349 category="error",
311 ignore_duplicate=True,
350 ignore_duplicate=True,
312 )
351 )
313 if c.repository_requirements_missing:
352 if c.repository_requirements_missing:
314 route = self.request.matched_route.name
353 route = self.request.matched_route.name
315 if route.startswith(("edit_repo", "repo_summary")):
354 if route.startswith(("edit_repo", "repo_summary")):
316 # allow summary and edit repo on missing requirements
355 # allow summary and edit repo on missing requirements
317 return c
356 return c
318
357
319 raise HTTPFound(
358 raise HTTPFound(
320 h.route_path("repo_summary", repo_name=self.db_repo_name)
359 h.route_path("repo_summary", repo_name=self.db_repo_name)
321 )
360 )
322
361
323 else: # redirect if we don't show missing requirements
362 else: # redirect if we don't show missing requirements
324 raise HTTPFound(h.route_path("home"))
363 raise HTTPFound(h.route_path("home"))
325
364
326 c.has_origin_repo_read_perm = False
365 c.has_origin_repo_read_perm = False
327 if self.db_repo.fork:
366 if self.db_repo.fork:
328 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
367 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
329 "repository.write", "repository.read", "repository.admin"
368 "repository.write", "repository.read", "repository.admin"
330 )(self.db_repo.fork.repo_name, "summary fork link")
369 )(self.db_repo.fork.repo_name, "summary fork link")
331
370
332 return c
371 return c
333
372
334 def _get_f_path_unchecked(self, matchdict, default=None):
373 def _get_f_path_unchecked(self, matchdict, default=None):
335 """
374 """
336 Should only be used by redirects, everything else should call _get_f_path
375 Should only be used by redirects, everything else should call _get_f_path
337 """
376 """
338 f_path = matchdict.get("f_path")
377 f_path = matchdict.get("f_path")
339 if f_path:
378 if f_path:
340 # fix for multiple initial slashes that causes errors for GIT
379 # fix for multiple initial slashes that causes errors for GIT
341 return f_path.lstrip("/")
380 return f_path.lstrip("/")
342
381
343 return default
382 return default
344
383
345 def _get_f_path(self, matchdict, default=None):
384 def _get_f_path(self, matchdict, default=None):
346 f_path_match = self._get_f_path_unchecked(matchdict, default)
385 f_path_match = self._get_f_path_unchecked(matchdict, default)
347 return self.path_filter.assert_path_permissions(f_path_match)
386 return self.path_filter.assert_path_permissions(f_path_match)
348
387
349 def _get_general_setting(self, target_repo, settings_key, default=False):
388 def _get_general_setting(self, target_repo, settings_key, default=False):
350 settings_model = VcsSettingsModel(repo=target_repo)
389 settings_model = VcsSettingsModel(repo=target_repo)
351 settings = settings_model.get_general_settings()
390 settings = settings_model.get_general_settings()
352 return settings.get(settings_key, default)
391 return settings.get(settings_key, default)
353
392
354 def _get_repo_setting(self, target_repo, settings_key, default=False):
393 def _get_repo_setting(self, target_repo, settings_key, default=False):
355 settings_model = VcsSettingsModel(repo=target_repo)
394 settings_model = VcsSettingsModel(repo=target_repo)
356 settings = settings_model.get_repo_settings_inherited()
395 settings = settings_model.get_repo_settings_inherited()
357 return settings.get(settings_key, default)
396 return settings.get(settings_key, default)
358
397
359 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path="/"):
398 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path="/"):
360 log.debug("Looking for README file at path %s", path)
399 log.debug("Looking for README file at path %s", path)
361 if commit_id:
400 if commit_id:
362 landing_commit_id = commit_id
401 landing_commit_id = commit_id
363 else:
402 else:
364 landing_commit = db_repo.get_landing_commit()
403 landing_commit = db_repo.get_landing_commit()
365 if isinstance(landing_commit, EmptyCommit):
404 if isinstance(landing_commit, EmptyCommit):
366 return None, None
405 return None, None
367 landing_commit_id = landing_commit.raw_id
406 landing_commit_id = landing_commit.raw_id
368
407
369 cache_namespace_uid = f"repo.{db_repo.repo_id}"
408 cache_namespace_uid = f"repo.{db_repo.repo_id}"
370 region = rc_cache.get_or_create_region(
409 region = rc_cache.get_or_create_region(
371 "cache_repo", cache_namespace_uid, use_async_runner=False
410 "cache_repo", cache_namespace_uid, use_async_runner=False
372 )
411 )
373 start = time.time()
412 start = time.time()
374
413
375 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
414 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
376 def generate_repo_readme(
415 def generate_repo_readme(
377 repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type
416 repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type
378 ):
417 ):
379 readme_data = None
418 readme_data = None
380 readme_filename = None
419 readme_filename = None
381
420
382 commit = db_repo.get_commit(_commit_id)
421 commit = db_repo.get_commit(_commit_id)
383 log.debug("Searching for a README file at commit %s.", _commit_id)
422 log.debug("Searching for a README file at commit %s.", _commit_id)
384 readme_node = ReadmeFinder(_renderer_type).search(
423 readme_node = ReadmeFinder(_renderer_type).search(
385 commit, path=_readme_search_path
424 commit, path=_readme_search_path
386 )
425 )
387
426
388 if readme_node:
427 if readme_node:
389 log.debug("Found README node: %s", readme_node)
428 log.debug("Found README node: %s", readme_node)
390
429
391 relative_urls = {
430 relative_urls = {
392 "raw": h.route_path(
431 "raw": h.route_path(
393 "repo_file_raw",
432 "repo_file_raw",
394 repo_name=_repo_name,
433 repo_name=_repo_name,
395 commit_id=commit.raw_id,
434 commit_id=commit.raw_id,
396 f_path=readme_node.path,
435 f_path=readme_node.path,
397 ),
436 ),
398 "standard": h.route_path(
437 "standard": h.route_path(
399 "repo_files",
438 "repo_files",
400 repo_name=_repo_name,
439 repo_name=_repo_name,
401 commit_id=commit.raw_id,
440 commit_id=commit.raw_id,
402 f_path=readme_node.path,
441 f_path=readme_node.path,
403 ),
442 ),
404 }
443 }
405
444
406 readme_data = self._render_readme_or_none(
445 readme_data = self._render_readme_or_none(
407 commit, readme_node, relative_urls
446 commit, readme_node, relative_urls
408 )
447 )
409 readme_filename = readme_node.str_path
448 readme_filename = readme_node.str_path
410
449
411 return readme_data, readme_filename
450 return readme_data, readme_filename
412
451
413 readme_data, readme_filename = generate_repo_readme(
452 readme_data, readme_filename = generate_repo_readme(
414 db_repo.repo_id,
453 db_repo.repo_id,
415 landing_commit_id,
454 landing_commit_id,
416 db_repo.repo_name,
455 db_repo.repo_name,
417 path,
456 path,
418 renderer_type,
457 renderer_type,
419 )
458 )
420
459
421 compute_time = time.time() - start
460 compute_time = time.time() - start
422 log.debug(
461 log.debug(
423 "Repo README for path %s generated and computed in %.4fs",
462 "Repo README for path %s generated and computed in %.4fs",
424 path,
463 path,
425 compute_time,
464 compute_time,
426 )
465 )
427 return readme_data, readme_filename
466 return readme_data, readme_filename
428
467
429 def _render_readme_or_none(self, commit, readme_node, relative_urls):
468 def _render_readme_or_none(self, commit, readme_node, relative_urls):
430 log.debug("Found README file `%s` rendering...", readme_node.path)
469 log.debug("Found README file `%s` rendering...", readme_node.path)
431 renderer = MarkupRenderer()
470 renderer = MarkupRenderer()
432 try:
471 try:
433 html_source = renderer.render(
472 html_source = renderer.render(
434 readme_node.str_content, filename=readme_node.path
473 readme_node.str_content, filename=readme_node.path
435 )
474 )
436 if relative_urls:
475 if relative_urls:
437 return relative_links(html_source, relative_urls)
476 return relative_links(html_source, relative_urls)
438 return html_source
477 return html_source
439 except Exception:
478 except Exception:
440 log.exception("Exception while trying to render the README")
479 log.exception("Exception while trying to render the README")
441
480
442 def get_recache_flag(self):
481 def get_recache_flag(self):
443 for flag_name in ["force_recache", "force-recache", "no-cache"]:
482 for flag_name in ["force_recache", "force-recache", "no-cache"]:
444 flag_val = self.request.GET.get(flag_name)
483 flag_val = self.request.GET.get(flag_name)
445 if str2bool(flag_val):
484 if str2bool(flag_val):
446 return True
485 return True
447 return False
486 return False
448
487
449 def get_commit_preload_attrs(cls):
488 def get_commit_preload_attrs(cls):
450 pre_load = [
489 pre_load = [
451 "author",
490 "author",
452 "branch",
491 "branch",
453 "date",
492 "date",
454 "message",
493 "message",
455 "parents",
494 "parents",
456 "obsolete",
495 "obsolete",
457 "phase",
496 "phase",
458 "hidden",
497 "hidden",
459 ]
498 ]
460 return pre_load
499 return pre_load
461
500
462
501
463 class PathFilter(object):
502 class PathFilter(object):
464 # Expects and instance of BasePathPermissionChecker or None
503 # Expects and instance of BasePathPermissionChecker or None
465 def __init__(self, permission_checker):
504 def __init__(self, permission_checker):
466 self.permission_checker = permission_checker
505 self.permission_checker = permission_checker
467
506
468 def assert_path_permissions(self, path):
507 def assert_path_permissions(self, path):
469 if self.path_access_allowed(path):
508 if self.path_access_allowed(path):
470 return path
509 return path
471 raise HTTPForbidden()
510 raise HTTPForbidden()
472
511
473 def path_access_allowed(self, path):
512 def path_access_allowed(self, path):
474 log.debug("Checking ACL permissions for PathFilter for `%s`", path)
513 log.debug("Checking ACL permissions for PathFilter for `%s`", path)
475 if self.permission_checker:
514 if self.permission_checker:
476 has_access = path and self.permission_checker.has_access(path)
515 has_access = path and self.permission_checker.has_access(path)
477 log.debug(
516 log.debug(
478 "ACL Permissions checker enabled, ACL Check has_access: %s", has_access
517 "ACL Permissions checker enabled, ACL Check has_access: %s", has_access
479 )
518 )
480 return has_access
519 return has_access
481
520
482 log.debug("ACL permissions checker not enabled, skipping...")
521 log.debug("ACL permissions checker not enabled, skipping...")
483 return True
522 return True
484
523
485 def filter_patchset(self, patchset):
524 def filter_patchset(self, patchset):
486 if not self.permission_checker or not patchset:
525 if not self.permission_checker or not patchset:
487 return patchset, False
526 return patchset, False
488 had_filtered = False
527 had_filtered = False
489 filtered_patchset = []
528 filtered_patchset = []
490 for patch in patchset:
529 for patch in patchset:
491 filename = patch.get("filename", None)
530 filename = patch.get("filename", None)
492 if not filename or self.permission_checker.has_access(filename):
531 if not filename or self.permission_checker.has_access(filename):
493 filtered_patchset.append(patch)
532 filtered_patchset.append(patch)
494 else:
533 else:
495 had_filtered = True
534 had_filtered = True
496 if had_filtered:
535 if had_filtered:
497 if isinstance(patchset, diffs.LimitedDiffContainer):
536 if isinstance(patchset, diffs.LimitedDiffContainer):
498 filtered_patchset = diffs.LimitedDiffContainer(
537 filtered_patchset = diffs.LimitedDiffContainer(
499 patchset.diff_limit, patchset.cur_diff_size, filtered_patchset
538 patchset.diff_limit, patchset.cur_diff_size, filtered_patchset
500 )
539 )
501 return filtered_patchset, True
540 return filtered_patchset, True
502 else:
541 else:
503 return patchset, False
542 return patchset, False
504
543
505 def render_patchset_filtered(
544 def render_patchset_filtered(
506 self, diffset, patchset, source_ref=None, target_ref=None
545 self, diffset, patchset, source_ref=None, target_ref=None
507 ):
546 ):
508 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
547 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
509 result = diffset.render_patchset(
548 result = diffset.render_patchset(
510 filtered_patchset, source_ref=source_ref, target_ref=target_ref
549 filtered_patchset, source_ref=source_ref, target_ref=target_ref
511 )
550 )
512 result.has_hidden_changes = has_hidden_changes
551 result.has_hidden_changes = has_hidden_changes
513 return result
552 return result
514
553
515 def get_raw_patch(self, diff_processor):
554 def get_raw_patch(self, diff_processor):
516 if self.permission_checker is None:
555 if self.permission_checker is None:
517 return diff_processor.as_raw()
556 return diff_processor.as_raw()
518 elif self.permission_checker.has_full_access:
557 elif self.permission_checker.has_full_access:
519 return diff_processor.as_raw()
558 return diff_processor.as_raw()
520 else:
559 else:
521 return "# Repository has user-specific filters, raw patch generation is disabled."
560 return "# Repository has user-specific filters, raw patch generation is disabled."
522
561
523 @property
562 @property
524 def is_enabled(self):
563 def is_enabled(self):
525 return self.permission_checker is not None
564 return self.permission_checker is not None
526
565
527
566
528 class RepoGroupAppView(BaseAppView):
567 class RepoGroupAppView(BaseAppView):
529 def __init__(self, context, request):
568 def __init__(self, context, request):
530 super().__init__(context, request)
569 super().__init__(context, request)
531 self.db_repo_group = request.db_repo_group
570 self.db_repo_group = request.db_repo_group
532 self.db_repo_group_name = self.db_repo_group.group_name
571 self.db_repo_group_name = self.db_repo_group.group_name
533
572
534 def _get_local_tmpl_context(self, include_app_defaults=True):
573 def _get_local_tmpl_context(self, include_app_defaults=True):
535 _ = self.request.translate
574 _ = self.request.translate
536 c = super()._get_local_tmpl_context(include_app_defaults=include_app_defaults)
575 c = super()._get_local_tmpl_context(include_app_defaults=include_app_defaults)
537 c.repo_group = self.db_repo_group
576 c.repo_group = self.db_repo_group
538 return c
577 return c
539
578
540 def _revoke_perms_on_yourself(self, form_result):
579 def _revoke_perms_on_yourself(self, form_result):
541 _updates = [
580 _updates = [
542 u
581 u
543 for u in form_result["perm_updates"]
582 for u in form_result["perm_updates"]
544 if self._rhodecode_user.user_id == int(u[0])
583 if self._rhodecode_user.user_id == int(u[0])
545 ]
584 ]
546 _additions = [
585 _additions = [
547 u
586 u
548 for u in form_result["perm_additions"]
587 for u in form_result["perm_additions"]
549 if self._rhodecode_user.user_id == int(u[0])
588 if self._rhodecode_user.user_id == int(u[0])
550 ]
589 ]
551 _deletions = [
590 _deletions = [
552 u
591 u
553 for u in form_result["perm_deletions"]
592 for u in form_result["perm_deletions"]
554 if self._rhodecode_user.user_id == int(u[0])
593 if self._rhodecode_user.user_id == int(u[0])
555 ]
594 ]
556 admin_perm = "group.admin"
595 admin_perm = "group.admin"
557 if (
596 if (
558 _updates
597 _updates
559 and _updates[0][1] != admin_perm
598 and _updates[0][1] != admin_perm
560 or _additions
599 or _additions
561 and _additions[0][1] != admin_perm
600 and _additions[0][1] != admin_perm
562 or _deletions
601 or _deletions
563 and _deletions[0][1] != admin_perm
602 and _deletions[0][1] != admin_perm
564 ):
603 ):
565 return True
604 return True
566 return False
605 return False
567
606
568
607
569 class UserGroupAppView(BaseAppView):
608 class UserGroupAppView(BaseAppView):
570 def __init__(self, context, request):
609 def __init__(self, context, request):
571 super().__init__(context, request)
610 super().__init__(context, request)
572 self.db_user_group = request.db_user_group
611 self.db_user_group = request.db_user_group
573 self.db_user_group_name = self.db_user_group.users_group_name
612 self.db_user_group_name = self.db_user_group.users_group_name
574
613
575
614
576 class UserAppView(BaseAppView):
615 class UserAppView(BaseAppView):
577 def __init__(self, context, request):
616 def __init__(self, context, request):
578 super().__init__(context, request)
617 super().__init__(context, request)
579 self.db_user = request.db_user
618 self.db_user = request.db_user
580 self.db_user_id = self.db_user.user_id
619 self.db_user_id = self.db_user.user_id
581
620
582 _ = self.request.translate
621 _ = self.request.translate
583 if not request.db_user_supports_default:
622 if not request.db_user_supports_default:
584 if self.db_user.username == User.DEFAULT_USER:
623 if self.db_user.username == User.DEFAULT_USER:
585 h.flash(
624 h.flash(
586 _("Editing user `{}` is disabled.".format(User.DEFAULT_USER)),
625 _("Editing user `{}` is disabled.".format(User.DEFAULT_USER)),
587 category="warning",
626 category="warning",
588 )
627 )
589 raise HTTPFound(h.route_path("users"))
628 raise HTTPFound(h.route_path("users"))
590
629
591
630
592 class DataGridAppView(object):
631 class DataGridAppView(object):
593 """
632 """
594 Common class to have re-usable grid rendering components
633 Common class to have re-usable grid rendering components
595 """
634 """
596
635
597 def _extract_ordering(self, request, column_map=None):
636 def _extract_ordering(self, request, column_map=None):
598 column_map = column_map or {}
637 column_map = column_map or {}
599 column_index = safe_int(request.GET.get("order[0][column]"))
638 column_index = safe_int(request.GET.get("order[0][column]"))
600 order_dir = request.GET.get("order[0][dir]", "desc")
639 order_dir = request.GET.get("order[0][dir]", "desc")
601 order_by = request.GET.get("columns[%s][data][sort]" % column_index, "name_raw")
640 order_by = request.GET.get("columns[%s][data][sort]" % column_index, "name_raw")
602
641
603 # translate datatable to DB columns
642 # translate datatable to DB columns
604 order_by = column_map.get(order_by) or order_by
643 order_by = column_map.get(order_by) or order_by
605
644
606 search_q = request.GET.get("search[value]")
645 search_q = request.GET.get("search[value]")
607 return search_q, order_by, order_dir
646 return search_q, order_by, order_dir
608
647
609 def _extract_chunk(self, request):
648 def _extract_chunk(self, request):
610 start = safe_int(request.GET.get("start"), 0)
649 start = safe_int(request.GET.get("start"), 0)
611 length = safe_int(request.GET.get("length"), 25)
650 length = safe_int(request.GET.get("length"), 25)
612 draw = safe_int(request.GET.get("draw"))
651 draw = safe_int(request.GET.get("draw"))
613 return draw, start, length
652 return draw, start, length
614
653
615 def _get_order_col(self, order_by, model):
654 def _get_order_col(self, order_by, model):
616 if isinstance(order_by, str):
655 if isinstance(order_by, str):
617 try:
656 try:
618 return operator.attrgetter(order_by)(model)
657 return operator.attrgetter(order_by)(model)
619 except AttributeError:
658 except AttributeError:
620 return None
659 return None
621 else:
660 else:
622 return order_by
661 return order_by
623
662
624
663
625 class BaseReferencesView(RepoAppView):
664 class BaseReferencesView(RepoAppView):
626 """
665 """
627 Base for reference view for branches, tags and bookmarks.
666 Base for reference view for branches, tags and bookmarks.
628 """
667 """
629
668
630 def load_default_context(self):
669 def load_default_context(self):
631 c = self._get_local_tmpl_context()
670 c = self._get_local_tmpl_context()
632 return c
671 return c
633
672
634 def load_refs_context(self, ref_items, partials_template):
673 def load_refs_context(self, ref_items, partials_template):
635 _render = self.request.get_partial_renderer(partials_template)
674 _render = self.request.get_partial_renderer(partials_template)
636 pre_load = ["author", "date", "message", "parents"]
675 pre_load = ["author", "date", "message", "parents"]
637
676
638 is_svn = h.is_svn(self.rhodecode_vcs_repo)
677 is_svn = h.is_svn(self.rhodecode_vcs_repo)
639 is_hg = h.is_hg(self.rhodecode_vcs_repo)
678 is_hg = h.is_hg(self.rhodecode_vcs_repo)
640
679
641 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
680 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
642
681
643 closed_refs = {}
682 closed_refs = {}
644 if is_hg:
683 if is_hg:
645 closed_refs = self.rhodecode_vcs_repo.branches_closed
684 closed_refs = self.rhodecode_vcs_repo.branches_closed
646
685
647 data = []
686 data = []
648 for ref_name, commit_id in ref_items:
687 for ref_name, commit_id in ref_items:
649 commit = self.rhodecode_vcs_repo.get_commit(
688 commit = self.rhodecode_vcs_repo.get_commit(
650 commit_id=commit_id, pre_load=pre_load
689 commit_id=commit_id, pre_load=pre_load
651 )
690 )
652 closed = ref_name in closed_refs
691 closed = ref_name in closed_refs
653
692
654 # TODO: johbo: Unify generation of reference links
693 # TODO: johbo: Unify generation of reference links
655 use_commit_id = "/" in ref_name or is_svn
694 use_commit_id = "/" in ref_name or is_svn
656
695
657 if use_commit_id:
696 if use_commit_id:
658 files_url = h.route_path(
697 files_url = h.route_path(
659 "repo_files",
698 "repo_files",
660 repo_name=self.db_repo_name,
699 repo_name=self.db_repo_name,
661 f_path=ref_name if is_svn else "",
700 f_path=ref_name if is_svn else "",
662 commit_id=commit_id,
701 commit_id=commit_id,
663 _query=dict(at=ref_name),
702 _query=dict(at=ref_name),
664 )
703 )
665
704
666 else:
705 else:
667 files_url = h.route_path(
706 files_url = h.route_path(
668 "repo_files",
707 "repo_files",
669 repo_name=self.db_repo_name,
708 repo_name=self.db_repo_name,
670 f_path=ref_name if is_svn else "",
709 f_path=ref_name if is_svn else "",
671 commit_id=ref_name,
710 commit_id=ref_name,
672 _query=dict(at=ref_name),
711 _query=dict(at=ref_name),
673 )
712 )
674
713
675 data.append(
714 data.append(
676 {
715 {
677 "name": _render("name", ref_name, files_url, closed),
716 "name": _render("name", ref_name, files_url, closed),
678 "name_raw": ref_name,
717 "name_raw": ref_name,
718 "closed": closed,
679 "date": _render("date", commit.date),
719 "date": _render("date", commit.date),
680 "date_raw": datetime_to_time(commit.date),
720 "date_raw": datetime_to_time(commit.date),
681 "author": _render("author", commit.author),
721 "author": _render("author", commit.author),
682 "commit": _render(
722 "commit": _render(
683 "commit", commit.message, commit.raw_id, commit.idx
723 "commit", commit.message, commit.raw_id, commit.idx
684 ),
724 ),
685 "commit_raw": commit.idx,
725 "commit_raw": commit.idx,
686 "compare": _render(
726 "compare": _render(
687 "compare", format_ref_id(ref_name, commit.raw_id)
727 "compare", format_ref_id(ref_name, commit.raw_id)
688 ),
728 ),
689 }
729 }
690 )
730 )
691
731
692 return data
732 return data
693
733
694
734
695 class RepoRoutePredicate(object):
735 class RepoRoutePredicate(object):
696 def __init__(self, val, config):
736 def __init__(self, val, config):
697 self.val = val
737 self.val = val
698
738
699 def text(self):
739 def text(self):
700 return f"repo_route = {self.val}"
740 return f"repo_route = {self.val}"
701
741
702 phash = text
742 phash = text
703
743
704 def __call__(self, info, request):
744 def __call__(self, info, request):
705 if hasattr(request, "vcs_call"):
745 if hasattr(request, "vcs_call"):
706 # skip vcs calls
746 # skip vcs calls
707 return
747 return
708
748
709 repo_name = info["match"]["repo_name"]
749 repo_name = info["match"]["repo_name"]
710
750
711 repo_name_parts = repo_name.split("/")
751 repo_name_parts = repo_name.split("/")
712 repo_slugs = [x for x in (repo_name_slug(x) for x in repo_name_parts)]
752 repo_slugs = [x for x in (repo_name_slug(x) for x in repo_name_parts)]
713
753
714 if repo_name_parts != repo_slugs:
754 if repo_name_parts != repo_slugs:
715 # short-skip if the repo-name doesn't follow slug rule
755 # short-skip if the repo-name doesn't follow slug rule
716 log.warning(
756 log.warning(
717 "repo_name: %s is different than slug %s", repo_name_parts, repo_slugs
757 "repo_name: %s is different than slug %s", repo_name_parts, repo_slugs
718 )
758 )
719 return False
759 return False
720
760
721 repo_model = repo.RepoModel()
761 repo_model = repo.RepoModel()
722
762
723 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
763 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
724
764
725 def redirect_if_creating(route_info, db_repo):
765 def redirect_if_creating(route_info, db_repo):
726 skip_views = ["edit_repo_advanced_delete"]
766 skip_views = ["edit_repo_advanced_delete"]
727 route = route_info["route"]
767 route = route_info["route"]
728 # we should skip delete view so we can actually "remove" repositories
768 # we should skip delete view so we can actually "remove" repositories
729 # if they get stuck in creating state.
769 # if they get stuck in creating state.
730 if route.name in skip_views:
770 if route.name in skip_views:
731 return
771 return
732
772
733 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
773 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
734 repo_creating_url = request.route_path(
774 repo_creating_url = request.route_path(
735 "repo_creating", repo_name=db_repo.repo_name
775 "repo_creating", repo_name=db_repo.repo_name
736 )
776 )
737 raise HTTPFound(repo_creating_url)
777 raise HTTPFound(repo_creating_url)
738
778
739 if by_name_match:
779 if by_name_match:
740 # register this as request object we can re-use later
780 # register this as request object we can re-use later
741 request.db_repo = by_name_match
781 request.db_repo = by_name_match
742 request.db_repo_name = request.db_repo.repo_name
782 request.db_repo_name = request.db_repo.repo_name
743
783
744 redirect_if_creating(info, by_name_match)
784 redirect_if_creating(info, by_name_match)
745 return True
785 return True
746
786
747 by_id_match = repo_model.get_repo_by_id(repo_name)
787 by_id_match = repo_model.get_repo_by_id(repo_name)
748 if by_id_match:
788 if by_id_match:
749 request.db_repo = by_id_match
789 request.db_repo = by_id_match
750 request.db_repo_name = request.db_repo.repo_name
790 request.db_repo_name = request.db_repo.repo_name
751 redirect_if_creating(info, by_id_match)
791 redirect_if_creating(info, by_id_match)
752 return True
792 return True
753
793
754 return False
794 return False
755
795
756
796
757 class RepoForbidArchivedRoutePredicate(object):
797 class RepoForbidArchivedRoutePredicate(object):
758 def __init__(self, val, config):
798 def __init__(self, val, config):
759 self.val = val
799 self.val = val
760
800
761 def text(self):
801 def text(self):
762 return f"repo_forbid_archived = {self.val}"
802 return f"repo_forbid_archived = {self.val}"
763
803
764 phash = text
804 phash = text
765
805
766 def __call__(self, info, request):
806 def __call__(self, info, request):
767 _ = request.translate
807 _ = request.translate
768 rhodecode_db_repo = request.db_repo
808 rhodecode_db_repo = request.db_repo
769
809
770 log.debug(
810 log.debug(
771 "%s checking if archived flag for repo for %s",
811 "%s checking if archived flag for repo for %s",
772 self.__class__.__name__,
812 self.__class__.__name__,
773 rhodecode_db_repo.repo_name,
813 rhodecode_db_repo.repo_name,
774 )
814 )
775
815
776 if rhodecode_db_repo.archived:
816 if rhodecode_db_repo.archived:
777 log.warning(
817 log.warning(
778 "Current view is not supported for archived repo:%s",
818 "Current view is not supported for archived repo:%s",
779 rhodecode_db_repo.repo_name,
819 rhodecode_db_repo.repo_name,
780 )
820 )
781
821
782 h.flash(
822 h.flash(
783 h.literal(_("Action not supported for archived repository.")),
823 h.literal(_("Action not supported for archived repository.")),
784 category="warning",
824 category="warning",
785 )
825 )
786 summary_url = request.route_path(
826 summary_url = request.route_path(
787 "repo_summary", repo_name=rhodecode_db_repo.repo_name
827 "repo_summary", repo_name=rhodecode_db_repo.repo_name
788 )
828 )
789 raise HTTPFound(summary_url)
829 raise HTTPFound(summary_url)
790 return True
830 return True
791
831
792
832
793 class RepoTypeRoutePredicate(object):
833 class RepoTypeRoutePredicate(object):
794 def __init__(self, val, config):
834 def __init__(self, val, config):
795 self.val = val or ["hg", "git", "svn"]
835 self.val = val or ["hg", "git", "svn"]
796
836
797 def text(self):
837 def text(self):
798 return f"repo_accepted_type = {self.val}"
838 return f"repo_accepted_type = {self.val}"
799
839
800 phash = text
840 phash = text
801
841
802 def __call__(self, info, request):
842 def __call__(self, info, request):
803 if hasattr(request, "vcs_call"):
843 if hasattr(request, "vcs_call"):
804 # skip vcs calls
844 # skip vcs calls
805 return
845 return
806
846
807 rhodecode_db_repo = request.db_repo
847 rhodecode_db_repo = request.db_repo
808
848
809 log.debug(
849 log.debug(
810 "%s checking repo type for %s in %s",
850 "%s checking repo type for %s in %s",
811 self.__class__.__name__,
851 self.__class__.__name__,
812 rhodecode_db_repo.repo_type,
852 rhodecode_db_repo.repo_type,
813 self.val,
853 self.val,
814 )
854 )
815
855
816 if rhodecode_db_repo.repo_type in self.val:
856 if rhodecode_db_repo.repo_type in self.val:
817 return True
857 return True
818 else:
858 else:
819 log.warning(
859 log.warning(
820 "Current view is not supported for repo type:%s",
860 "Current view is not supported for repo type:%s",
821 rhodecode_db_repo.repo_type,
861 rhodecode_db_repo.repo_type,
822 )
862 )
823 return False
863 return False
824
864
825
865
826 class RepoGroupRoutePredicate(object):
866 class RepoGroupRoutePredicate(object):
827 def __init__(self, val, config):
867 def __init__(self, val, config):
828 self.val = val
868 self.val = val
829
869
830 def text(self):
870 def text(self):
831 return f"repo_group_route = {self.val}"
871 return f"repo_group_route = {self.val}"
832
872
833 phash = text
873 phash = text
834
874
835 def __call__(self, info, request):
875 def __call__(self, info, request):
836 if hasattr(request, "vcs_call"):
876 if hasattr(request, "vcs_call"):
837 # skip vcs calls
877 # skip vcs calls
838 return
878 return
839
879
840 repo_group_name = info["match"]["repo_group_name"]
880 repo_group_name = info["match"]["repo_group_name"]
841
881
842 repo_group_name_parts = repo_group_name.split("/")
882 repo_group_name_parts = repo_group_name.split("/")
843 repo_group_slugs = [
883 repo_group_slugs = [
844 x for x in [repo_name_slug(x) for x in repo_group_name_parts]
884 x for x in [repo_name_slug(x) for x in repo_group_name_parts]
845 ]
885 ]
846 if repo_group_name_parts != repo_group_slugs:
886 if repo_group_name_parts != repo_group_slugs:
847 # short-skip if the repo-name doesn't follow slug rule
887 # short-skip if the repo-name doesn't follow slug rule
848 log.warning(
888 log.warning(
849 "repo_group_name: %s is different than slug %s",
889 "repo_group_name: %s is different than slug %s",
850 repo_group_name_parts,
890 repo_group_name_parts,
851 repo_group_slugs,
891 repo_group_slugs,
852 )
892 )
853 return False
893 return False
854
894
855 repo_group_model = repo_group.RepoGroupModel()
895 repo_group_model = repo_group.RepoGroupModel()
856 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
896 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
857
897
858 if by_name_match:
898 if by_name_match:
859 # register this as request object we can re-use later
899 # register this as request object we can re-use later
860 request.db_repo_group = by_name_match
900 request.db_repo_group = by_name_match
861 request.db_repo_group_name = request.db_repo_group.group_name
901 request.db_repo_group_name = request.db_repo_group.group_name
862 return True
902 return True
863
903
864 return False
904 return False
865
905
866
906
867 class UserGroupRoutePredicate(object):
907 class UserGroupRoutePredicate(object):
868 def __init__(self, val, config):
908 def __init__(self, val, config):
869 self.val = val
909 self.val = val
870
910
871 def text(self):
911 def text(self):
872 return f"user_group_route = {self.val}"
912 return f"user_group_route = {self.val}"
873
913
874 phash = text
914 phash = text
875
915
876 def __call__(self, info, request):
916 def __call__(self, info, request):
877 if hasattr(request, "vcs_call"):
917 if hasattr(request, "vcs_call"):
878 # skip vcs calls
918 # skip vcs calls
879 return
919 return
880
920
881 user_group_id = info["match"]["user_group_id"]
921 user_group_id = info["match"]["user_group_id"]
882 user_group_model = user_group.UserGroup()
922 user_group_model = user_group.UserGroup()
883 by_id_match = user_group_model.get(user_group_id, cache=False)
923 by_id_match = user_group_model.get(user_group_id, cache=False)
884
924
885 if by_id_match:
925 if by_id_match:
886 # register this as request object we can re-use later
926 # register this as request object we can re-use later
887 request.db_user_group = by_id_match
927 request.db_user_group = by_id_match
888 return True
928 return True
889
929
890 return False
930 return False
891
931
892
932
893 class UserRoutePredicateBase(object):
933 class UserRoutePredicateBase(object):
894 supports_default = None
934 supports_default = None
895
935
896 def __init__(self, val, config):
936 def __init__(self, val, config):
897 self.val = val
937 self.val = val
898
938
899 def text(self):
939 def text(self):
900 raise NotImplementedError()
940 raise NotImplementedError()
901
941
902 def __call__(self, info, request):
942 def __call__(self, info, request):
903 if hasattr(request, "vcs_call"):
943 if hasattr(request, "vcs_call"):
904 # skip vcs calls
944 # skip vcs calls
905 return
945 return
906
946
907 user_id = info["match"]["user_id"]
947 user_id = info["match"]["user_id"]
908 user_model = user.User()
948 user_model = user.User()
909 by_id_match = user_model.get(user_id, cache=False)
949 by_id_match = user_model.get(user_id, cache=False)
910
950
911 if by_id_match:
951 if by_id_match:
912 # register this as request object we can re-use later
952 # register this as request object we can re-use later
913 request.db_user = by_id_match
953 request.db_user = by_id_match
914 request.db_user_supports_default = self.supports_default
954 request.db_user_supports_default = self.supports_default
915 return True
955 return True
916
956
917 return False
957 return False
918
958
919
959
920 class UserRoutePredicate(UserRoutePredicateBase):
960 class UserRoutePredicate(UserRoutePredicateBase):
921 supports_default = False
961 supports_default = False
922
962
923 def text(self):
963 def text(self):
924 return f"user_route = {self.val}"
964 return f"user_route = {self.val}"
925
965
926 phash = text
966 phash = text
927
967
928
968
929 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
969 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
930 supports_default = True
970 supports_default = True
931
971
932 def text(self):
972 def text(self):
933 return f"user_with_default_route = {self.val}"
973 return f"user_with_default_route = {self.val}"
934
974
935 phash = text
975 phash = text
936
976
937
977
938 def includeme(config):
978 def includeme(config):
939 config.add_route_predicate("repo_route", RepoRoutePredicate)
979 config.add_route_predicate("repo_route", RepoRoutePredicate)
940 config.add_route_predicate("repo_accepted_types", RepoTypeRoutePredicate)
980 config.add_route_predicate("repo_accepted_types", RepoTypeRoutePredicate)
941 config.add_route_predicate(
981 config.add_route_predicate(
942 "repo_forbid_when_archived", RepoForbidArchivedRoutePredicate
982 "repo_forbid_when_archived", RepoForbidArchivedRoutePredicate
943 )
983 )
944 config.add_route_predicate("repo_group_route", RepoGroupRoutePredicate)
984 config.add_route_predicate("repo_group_route", RepoGroupRoutePredicate)
945 config.add_route_predicate("user_group_route", UserGroupRoutePredicate)
985 config.add_route_predicate("user_group_route", UserGroupRoutePredicate)
946 config.add_route_predicate("user_route_with_default", UserRouteWithDefaultPredicate)
986 config.add_route_predicate("user_route_with_default", UserRouteWithDefaultPredicate)
947 config.add_route_predicate("user_route", UserRoutePredicate)
987 config.add_route_predicate("user_route", UserRoutePredicate)
@@ -1,497 +1,497 b''
1
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import urllib.request
20 import urllib.request
21 import urllib.parse
21 import urllib.parse
22 import urllib.error
22 import urllib.error
23
23
24 import mock
24 import mock
25 import pytest
25 import pytest
26
26
27 from rhodecode.apps._base import ADMIN_PREFIX
27 from rhodecode.apps._base import ADMIN_PREFIX
28 from rhodecode.lib import auth
28 from rhodecode.lib import auth
29 from rhodecode.lib.utils2 import safe_str
29 from rhodecode.lib.utils2 import safe_str
30 from rhodecode.lib import helpers as h
30 from rhodecode.lib import helpers as h
31 from rhodecode.model.db import (
31 from rhodecode.model.db import (
32 Repository, RepoGroup, UserRepoToPerm, User, Permission)
32 Repository, RepoGroup, UserRepoToPerm, User, Permission)
33 from rhodecode.model.meta import Session
33 from rhodecode.model.meta import Session
34 from rhodecode.model.repo import RepoModel
34 from rhodecode.model.repo import RepoModel
35 from rhodecode.model.repo_group import RepoGroupModel
35 from rhodecode.model.repo_group import RepoGroupModel
36 from rhodecode.model.user import UserModel
36 from rhodecode.model.user import UserModel
37 from rhodecode.tests import (
37 from rhodecode.tests import (
38 login_user_session, assert_session_flash, TEST_USER_ADMIN_LOGIN,
38 login_user_session, assert_session_flash, TEST_USER_ADMIN_LOGIN,
39 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
39 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
40 from rhodecode.tests.fixture import Fixture, error_function
40 from rhodecode.tests.fixture import Fixture, error_function
41 from rhodecode.tests.utils import repo_on_filesystem
41 from rhodecode.tests.utils import repo_on_filesystem
42 from rhodecode.tests.routes import route_path
42 from rhodecode.tests.routes import route_path
43
43
44 fixture = Fixture()
44 fixture = Fixture()
45
45
46
46
47 def _get_permission_for_user(user, repo):
47 def _get_permission_for_user(user, repo):
48 perm = UserRepoToPerm.query()\
48 perm = UserRepoToPerm.query()\
49 .filter(UserRepoToPerm.repository ==
49 .filter(UserRepoToPerm.repository ==
50 Repository.get_by_repo_name(repo))\
50 Repository.get_by_repo_name(repo))\
51 .filter(UserRepoToPerm.user == User.get_by_username(user))\
51 .filter(UserRepoToPerm.user == User.get_by_username(user))\
52 .all()
52 .all()
53 return perm
53 return perm
54
54
55
55
56 @pytest.mark.usefixtures("app")
56 @pytest.mark.usefixtures("app")
57 class TestAdminRepos(object):
57 class TestAdminRepos(object):
58
58
59 def test_repo_list(self, autologin_user, user_util, xhr_header):
59 def test_repo_list(self, autologin_user, user_util, xhr_header):
60 repo = user_util.create_repo()
60 repo = user_util.create_repo()
61 repo_name = repo.repo_name
61 repo_name = repo.repo_name
62 response = self.app.get(
62 response = self.app.get(
63 route_path('repos_data'), status=200,
63 route_path('repos_data'), status=200,
64 extra_environ=xhr_header)
64 extra_environ=xhr_header)
65
65
66 response.mustcontain(repo_name)
66 response.mustcontain(repo_name)
67
67
68 def test_create_page_restricted_to_single_backend(self, autologin_user, backend):
68 def test_create_page_restricted_to_single_backend(self, autologin_user, backend):
69 with mock.patch('rhodecode.BACKENDS', {'git': 'git'}):
69 with mock.patch('rhodecode.BACKENDS', {'git': 'git'}):
70 response = self.app.get(route_path('repo_new'), status=200)
70 response = self.app.get(route_path('repo_new'), status=200)
71 assert_response = response.assert_response()
71 assert_response = response.assert_response()
72 element = assert_response.get_element('[name=repo_type]')
72 element = assert_response.get_element('[name=repo_type]')
73 assert element.get('value') == 'git'
73 assert element.get('value') == 'git'
74
74
75 def test_create_page_non_restricted_backends(self, autologin_user, backend):
75 def test_create_page_non_restricted_backends(self, autologin_user, backend):
76 response = self.app.get(route_path('repo_new'), status=200)
76 response = self.app.get(route_path('repo_new'), status=200)
77 assert_response = response.assert_response()
77 assert_response = response.assert_response()
78 assert ['hg', 'git', 'svn'] == [x.get('value') for x in assert_response.get_elements('[name=repo_type]')]
78 assert ['hg', 'git', 'svn'] == [x.get('value') for x in assert_response.get_elements('[name=repo_type]')]
79
79
80 @pytest.mark.parametrize(
80 @pytest.mark.parametrize(
81 "suffix", ['', 'xxa'], ids=['', 'non-ascii'])
81 "suffix", ['', 'xxa'], ids=['', 'non-ascii'])
82 def test_create(self, autologin_user, backend, suffix, csrf_token):
82 def test_create(self, autologin_user, backend, suffix, csrf_token):
83 repo_name_unicode = backend.new_repo_name(suffix=suffix)
83 repo_name_unicode = backend.new_repo_name(suffix=suffix)
84 repo_name = repo_name_unicode
84 repo_name = repo_name_unicode
85
85
86 description_unicode = 'description for newly created repo' + suffix
86 description_unicode = 'description for newly created repo' + suffix
87 description = description_unicode
87 description = description_unicode
88
88
89 response = self.app.post(
89 response = self.app.post(
90 route_path('repo_create'),
90 route_path('repo_create'),
91 fixture._get_repo_create_params(
91 fixture._get_repo_create_params(
92 repo_private=False,
92 repo_private=False,
93 repo_name=repo_name,
93 repo_name=repo_name,
94 repo_type=backend.alias,
94 repo_type=backend.alias,
95 repo_description=description,
95 repo_description=description,
96 csrf_token=csrf_token),
96 csrf_token=csrf_token),
97 status=302)
97 status=302)
98
98
99 self.assert_repository_is_created_correctly(
99 self.assert_repository_is_created_correctly(
100 repo_name, description, backend)
100 repo_name, description, backend)
101
101
102 def test_create_numeric_name(self, autologin_user, backend, csrf_token):
102 def test_create_numeric_name(self, autologin_user, backend, csrf_token):
103 numeric_repo = '1234'
103 numeric_repo = '1234'
104 repo_name = numeric_repo
104 repo_name = numeric_repo
105 description = 'description for newly created repo' + numeric_repo
105 description = 'description for newly created repo' + numeric_repo
106 self.app.post(
106 self.app.post(
107 route_path('repo_create'),
107 route_path('repo_create'),
108 fixture._get_repo_create_params(
108 fixture._get_repo_create_params(
109 repo_private=False,
109 repo_private=False,
110 repo_name=repo_name,
110 repo_name=repo_name,
111 repo_type=backend.alias,
111 repo_type=backend.alias,
112 repo_description=description,
112 repo_description=description,
113 csrf_token=csrf_token))
113 csrf_token=csrf_token))
114
114
115 self.assert_repository_is_created_correctly(
115 self.assert_repository_is_created_correctly(
116 repo_name, description, backend)
116 repo_name, description, backend)
117
117
118 @pytest.mark.parametrize("suffix", ['', '_ąćę'], ids=['', 'non-ascii'])
118 @pytest.mark.parametrize("suffix", ['', '_ąćę'], ids=['', 'non-ascii'])
119 def test_create_in_group(
119 def test_create_in_group(
120 self, autologin_user, backend, suffix, csrf_token):
120 self, autologin_user, backend, suffix, csrf_token):
121 # create GROUP
121 # create GROUP
122 group_name = f'sometest_{backend.alias}'
122 group_name = f'sometest_{backend.alias}'
123 gr = RepoGroupModel().create(group_name=group_name,
123 gr = RepoGroupModel().create(group_name=group_name,
124 group_description='test',
124 group_description='test',
125 owner=TEST_USER_ADMIN_LOGIN)
125 owner=TEST_USER_ADMIN_LOGIN)
126 Session().commit()
126 Session().commit()
127
127
128 repo_name = f'ingroup{suffix}'
128 repo_name = f'ingroup{suffix}'
129 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
129 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
130 description = 'description for newly created repo'
130 description = 'description for newly created repo'
131
131
132 self.app.post(
132 self.app.post(
133 route_path('repo_create'),
133 route_path('repo_create'),
134 fixture._get_repo_create_params(
134 fixture._get_repo_create_params(
135 repo_private=False,
135 repo_private=False,
136 repo_name=safe_str(repo_name),
136 repo_name=safe_str(repo_name),
137 repo_type=backend.alias,
137 repo_type=backend.alias,
138 repo_description=description,
138 repo_description=description,
139 repo_group=gr.group_id,
139 repo_group=gr.group_id,
140 csrf_token=csrf_token))
140 csrf_token=csrf_token))
141
141
142 # TODO: johbo: Cleanup work to fixture
142 # TODO: johbo: Cleanup work to fixture
143 try:
143 try:
144 self.assert_repository_is_created_correctly(
144 self.assert_repository_is_created_correctly(
145 repo_name_full, description, backend)
145 repo_name_full, description, backend)
146
146
147 new_repo = RepoModel().get_by_repo_name(repo_name_full)
147 new_repo = RepoModel().get_by_repo_name(repo_name_full)
148 inherited_perms = UserRepoToPerm.query().filter(
148 inherited_perms = UserRepoToPerm.query().filter(
149 UserRepoToPerm.repository_id == new_repo.repo_id).all()
149 UserRepoToPerm.repository_id == new_repo.repo_id).all()
150 assert len(inherited_perms) == 1
150 assert len(inherited_perms) == 1
151 finally:
151 finally:
152 RepoModel().delete(repo_name_full)
152 RepoModel().delete(repo_name_full)
153 RepoGroupModel().delete(group_name)
153 RepoGroupModel().delete(group_name)
154 Session().commit()
154 Session().commit()
155
155
156 def test_create_in_group_numeric_name(
156 def test_create_in_group_numeric_name(
157 self, autologin_user, backend, csrf_token):
157 self, autologin_user, backend, csrf_token):
158 # create GROUP
158 # create GROUP
159 group_name = 'sometest_%s' % backend.alias
159 group_name = 'sometest_%s' % backend.alias
160 gr = RepoGroupModel().create(group_name=group_name,
160 gr = RepoGroupModel().create(group_name=group_name,
161 group_description='test',
161 group_description='test',
162 owner=TEST_USER_ADMIN_LOGIN)
162 owner=TEST_USER_ADMIN_LOGIN)
163 Session().commit()
163 Session().commit()
164
164
165 repo_name = '12345'
165 repo_name = '12345'
166 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
166 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
167 description = 'description for newly created repo'
167 description = 'description for newly created repo'
168 self.app.post(
168 self.app.post(
169 route_path('repo_create'),
169 route_path('repo_create'),
170 fixture._get_repo_create_params(
170 fixture._get_repo_create_params(
171 repo_private=False,
171 repo_private=False,
172 repo_name=repo_name,
172 repo_name=repo_name,
173 repo_type=backend.alias,
173 repo_type=backend.alias,
174 repo_description=description,
174 repo_description=description,
175 repo_group=gr.group_id,
175 repo_group=gr.group_id,
176 csrf_token=csrf_token))
176 csrf_token=csrf_token))
177
177
178 # TODO: johbo: Cleanup work to fixture
178 # TODO: johbo: Cleanup work to fixture
179 try:
179 try:
180 self.assert_repository_is_created_correctly(
180 self.assert_repository_is_created_correctly(
181 repo_name_full, description, backend)
181 repo_name_full, description, backend)
182
182
183 new_repo = RepoModel().get_by_repo_name(repo_name_full)
183 new_repo = RepoModel().get_by_repo_name(repo_name_full)
184 inherited_perms = UserRepoToPerm.query()\
184 inherited_perms = UserRepoToPerm.query()\
185 .filter(UserRepoToPerm.repository_id == new_repo.repo_id).all()
185 .filter(UserRepoToPerm.repository_id == new_repo.repo_id).all()
186 assert len(inherited_perms) == 1
186 assert len(inherited_perms) == 1
187 finally:
187 finally:
188 RepoModel().delete(repo_name_full)
188 RepoModel().delete(repo_name_full)
189 RepoGroupModel().delete(group_name)
189 RepoGroupModel().delete(group_name)
190 Session().commit()
190 Session().commit()
191
191
192 def test_create_in_group_without_needed_permissions(self, backend):
192 def test_create_in_group_without_needed_permissions(self, backend):
193 session = login_user_session(
193 session = login_user_session(
194 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
194 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
195 csrf_token = auth.get_csrf_token(session)
195 csrf_token = auth.get_csrf_token(session)
196 # revoke
196 # revoke
197 user_model = UserModel()
197 user_model = UserModel()
198 # disable fork and create on default user
198 # disable fork and create on default user
199 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
199 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
200 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
200 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
201 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
201 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
202 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
202 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
203
203
204 # disable on regular user
204 # disable on regular user
205 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
205 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
206 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
206 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
207 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
207 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
208 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
208 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
209 Session().commit()
209 Session().commit()
210
210
211 # create GROUP
211 # create GROUP
212 group_name = 'reg_sometest_%s' % backend.alias
212 group_name = 'reg_sometest_%s' % backend.alias
213 gr = RepoGroupModel().create(group_name=group_name,
213 gr = RepoGroupModel().create(group_name=group_name,
214 group_description='test',
214 group_description='test',
215 owner=TEST_USER_ADMIN_LOGIN)
215 owner=TEST_USER_ADMIN_LOGIN)
216 Session().commit()
216 Session().commit()
217 repo_group_id = gr.group_id
217 repo_group_id = gr.group_id
218
218
219 group_name_allowed = 'reg_sometest_allowed_%s' % backend.alias
219 group_name_allowed = 'reg_sometest_allowed_%s' % backend.alias
220 gr_allowed = RepoGroupModel().create(
220 gr_allowed = RepoGroupModel().create(
221 group_name=group_name_allowed,
221 group_name=group_name_allowed,
222 group_description='test',
222 group_description='test',
223 owner=TEST_USER_REGULAR_LOGIN)
223 owner=TEST_USER_REGULAR_LOGIN)
224 allowed_repo_group_id = gr_allowed.group_id
224 allowed_repo_group_id = gr_allowed.group_id
225 Session().commit()
225 Session().commit()
226
226
227 repo_name = 'ingroup'
227 repo_name = 'ingroup'
228 description = 'description for newly created repo'
228 description = 'description for newly created repo'
229 response = self.app.post(
229 response = self.app.post(
230 route_path('repo_create'),
230 route_path('repo_create'),
231 fixture._get_repo_create_params(
231 fixture._get_repo_create_params(
232 repo_private=False,
232 repo_private=False,
233 repo_name=repo_name,
233 repo_name=repo_name,
234 repo_type=backend.alias,
234 repo_type=backend.alias,
235 repo_description=description,
235 repo_description=description,
236 repo_group=repo_group_id,
236 repo_group=repo_group_id,
237 csrf_token=csrf_token))
237 csrf_token=csrf_token))
238
238
239 response.mustcontain('Invalid value')
239 response.mustcontain('Invalid value')
240
240
241 # user is allowed to create in this group
241 # user is allowed to create in this group
242 repo_name = 'ingroup'
242 repo_name = 'ingroup'
243 repo_name_full = RepoGroup.url_sep().join(
243 repo_name_full = RepoGroup.url_sep().join(
244 [group_name_allowed, repo_name])
244 [group_name_allowed, repo_name])
245 description = 'description for newly created repo'
245 description = 'description for newly created repo'
246 response = self.app.post(
246 response = self.app.post(
247 route_path('repo_create'),
247 route_path('repo_create'),
248 fixture._get_repo_create_params(
248 fixture._get_repo_create_params(
249 repo_private=False,
249 repo_private=False,
250 repo_name=repo_name,
250 repo_name=repo_name,
251 repo_type=backend.alias,
251 repo_type=backend.alias,
252 repo_description=description,
252 repo_description=description,
253 repo_group=allowed_repo_group_id,
253 repo_group=allowed_repo_group_id,
254 csrf_token=csrf_token))
254 csrf_token=csrf_token))
255
255
256 # TODO: johbo: Cleanup in pytest fixture
256 # TODO: johbo: Cleanup in pytest fixture
257 try:
257 try:
258 self.assert_repository_is_created_correctly(
258 self.assert_repository_is_created_correctly(
259 repo_name_full, description, backend)
259 repo_name_full, description, backend)
260
260
261 new_repo = RepoModel().get_by_repo_name(repo_name_full)
261 new_repo = RepoModel().get_by_repo_name(repo_name_full)
262 inherited_perms = UserRepoToPerm.query().filter(
262 inherited_perms = UserRepoToPerm.query().filter(
263 UserRepoToPerm.repository_id == new_repo.repo_id).all()
263 UserRepoToPerm.repository_id == new_repo.repo_id).all()
264 assert len(inherited_perms) == 1
264 assert len(inherited_perms) == 1
265
265
266 assert repo_on_filesystem(repo_name_full)
266 assert repo_on_filesystem(repo_name_full)
267 finally:
267 finally:
268 RepoModel().delete(repo_name_full)
268 RepoModel().delete(repo_name_full)
269 RepoGroupModel().delete(group_name)
269 RepoGroupModel().delete(group_name)
270 RepoGroupModel().delete(group_name_allowed)
270 RepoGroupModel().delete(group_name_allowed)
271 Session().commit()
271 Session().commit()
272
272
273 def test_create_in_group_inherit_permissions(self, autologin_user, backend,
273 def test_create_in_group_inherit_permissions(self, autologin_user, backend,
274 csrf_token):
274 csrf_token):
275 # create GROUP
275 # create GROUP
276 group_name = 'sometest_%s' % backend.alias
276 group_name = 'sometest_%s' % backend.alias
277 gr = RepoGroupModel().create(group_name=group_name,
277 gr = RepoGroupModel().create(group_name=group_name,
278 group_description='test',
278 group_description='test',
279 owner=TEST_USER_ADMIN_LOGIN)
279 owner=TEST_USER_ADMIN_LOGIN)
280 perm = Permission.get_by_key('repository.write')
280 perm = Permission.get_by_key('repository.write')
281 RepoGroupModel().grant_user_permission(
281 RepoGroupModel().grant_user_permission(
282 gr, TEST_USER_REGULAR_LOGIN, perm)
282 gr, TEST_USER_REGULAR_LOGIN, perm)
283
283
284 # add repo permissions
284 # add repo permissions
285 Session().commit()
285 Session().commit()
286 repo_group_id = gr.group_id
286 repo_group_id = gr.group_id
287 repo_name = 'ingroup_inherited_%s' % backend.alias
287 repo_name = 'ingroup_inherited_%s' % backend.alias
288 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
288 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
289 description = 'description for newly created repo'
289 description = 'description for newly created repo'
290 self.app.post(
290 self.app.post(
291 route_path('repo_create'),
291 route_path('repo_create'),
292 fixture._get_repo_create_params(
292 fixture._get_repo_create_params(
293 repo_private=False,
293 repo_private=False,
294 repo_name=repo_name,
294 repo_name=repo_name,
295 repo_type=backend.alias,
295 repo_type=backend.alias,
296 repo_description=description,
296 repo_description=description,
297 repo_group=repo_group_id,
297 repo_group=repo_group_id,
298 repo_copy_permissions=True,
298 repo_copy_permissions=True,
299 csrf_token=csrf_token))
299 csrf_token=csrf_token))
300
300
301 # TODO: johbo: Cleanup to pytest fixture
301 # TODO: johbo: Cleanup to pytest fixture
302 try:
302 try:
303 self.assert_repository_is_created_correctly(
303 self.assert_repository_is_created_correctly(
304 repo_name_full, description, backend)
304 repo_name_full, description, backend)
305 except Exception:
305 except Exception:
306 RepoGroupModel().delete(group_name)
306 RepoGroupModel().delete(group_name)
307 Session().commit()
307 Session().commit()
308 raise
308 raise
309
309
310 # check if inherited permissions are applied
310 # check if inherited permissions are applied
311 new_repo = RepoModel().get_by_repo_name(repo_name_full)
311 new_repo = RepoModel().get_by_repo_name(repo_name_full)
312 inherited_perms = UserRepoToPerm.query().filter(
312 inherited_perms = UserRepoToPerm.query().filter(
313 UserRepoToPerm.repository_id == new_repo.repo_id).all()
313 UserRepoToPerm.repository_id == new_repo.repo_id).all()
314 assert len(inherited_perms) == 2
314 assert len(inherited_perms) == 2
315
315
316 assert TEST_USER_REGULAR_LOGIN in [
316 assert TEST_USER_REGULAR_LOGIN in [
317 x.user.username for x in inherited_perms]
317 x.user.username for x in inherited_perms]
318 assert 'repository.write' in [
318 assert 'repository.write' in [
319 x.permission.permission_name for x in inherited_perms]
319 x.permission.permission_name for x in inherited_perms]
320
320
321 RepoModel().delete(repo_name_full)
321 RepoModel().delete(repo_name_full)
322 RepoGroupModel().delete(group_name)
322 RepoGroupModel().delete(group_name)
323 Session().commit()
323 Session().commit()
324
324
325 @pytest.mark.xfail_backends(
325 @pytest.mark.xfail_backends(
326 "git", "hg", reason="Missing reposerver support")
326 "git", "hg", reason="Missing reposerver support")
327 def test_create_with_clone_uri(self, autologin_user, backend, reposerver,
327 def test_create_with_clone_uri(self, autologin_user, backend, reposerver,
328 csrf_token):
328 csrf_token):
329 source_repo = backend.create_repo(number_of_commits=2)
329 source_repo = backend.create_repo(number_of_commits=2)
330 source_repo_name = source_repo.repo_name
330 source_repo_name = source_repo.repo_name
331 reposerver.serve(source_repo.scm_instance())
331 reposerver.serve(source_repo.scm_instance())
332
332
333 repo_name = backend.new_repo_name()
333 repo_name = backend.new_repo_name()
334 response = self.app.post(
334 response = self.app.post(
335 route_path('repo_create'),
335 route_path('repo_create'),
336 fixture._get_repo_create_params(
336 fixture._get_repo_create_params(
337 repo_private=False,
337 repo_private=False,
338 repo_name=repo_name,
338 repo_name=repo_name,
339 repo_type=backend.alias,
339 repo_type=backend.alias,
340 repo_description='',
340 repo_description='',
341 clone_uri=reposerver.url,
341 clone_uri=reposerver.url,
342 csrf_token=csrf_token),
342 csrf_token=csrf_token),
343 status=302)
343 status=302)
344
344
345 # Should be redirected to the creating page
345 # Should be redirected to the creating page
346 response.mustcontain('repo_creating')
346 response.mustcontain('repo_creating')
347
347
348 # Expecting that both repositories have same history
348 # Expecting that both repositories have same history
349 source_repo = RepoModel().get_by_repo_name(source_repo_name)
349 source_repo = RepoModel().get_by_repo_name(source_repo_name)
350 source_vcs = source_repo.scm_instance()
350 source_vcs = source_repo.scm_instance()
351 repo = RepoModel().get_by_repo_name(repo_name)
351 repo = RepoModel().get_by_repo_name(repo_name)
352 repo_vcs = repo.scm_instance()
352 repo_vcs = repo.scm_instance()
353 assert source_vcs[0].message == repo_vcs[0].message
353 assert source_vcs[0].message == repo_vcs[0].message
354 assert source_vcs.count() == repo_vcs.count()
354 assert source_vcs.count() == repo_vcs.count()
355 assert source_vcs.commit_ids == repo_vcs.commit_ids
355 assert source_vcs.commit_ids == repo_vcs.commit_ids
356
356
357 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
357 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
358 def test_create_remote_repo_wrong_clone_uri(self, autologin_user, backend,
358 def test_create_remote_repo_wrong_clone_uri(self, autologin_user, backend,
359 csrf_token):
359 csrf_token):
360 repo_name = backend.new_repo_name()
360 repo_name = backend.new_repo_name()
361 description = 'description for newly created repo'
361 description = 'description for newly created repo'
362 response = self.app.post(
362 response = self.app.post(
363 route_path('repo_create'),
363 route_path('repo_create'),
364 fixture._get_repo_create_params(
364 fixture._get_repo_create_params(
365 repo_private=False,
365 repo_private=False,
366 repo_name=repo_name,
366 repo_name=repo_name,
367 repo_type=backend.alias,
367 repo_type=backend.alias,
368 repo_description=description,
368 repo_description=description,
369 clone_uri='http://repo.invalid/repo',
369 clone_uri='http://repo.invalid/repo',
370 csrf_token=csrf_token))
370 csrf_token=csrf_token))
371 response.mustcontain('invalid clone url')
371 response.mustcontain('invalid clone url')
372
372
373 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
373 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
374 def test_create_remote_repo_wrong_clone_uri_hg_svn(
374 def test_create_remote_repo_wrong_clone_uri_hg_svn(
375 self, autologin_user, backend, csrf_token):
375 self, autologin_user, backend, csrf_token):
376 repo_name = backend.new_repo_name()
376 repo_name = backend.new_repo_name()
377 description = 'description for newly created repo'
377 description = 'description for newly created repo'
378 response = self.app.post(
378 response = self.app.post(
379 route_path('repo_create'),
379 route_path('repo_create'),
380 fixture._get_repo_create_params(
380 fixture._get_repo_create_params(
381 repo_private=False,
381 repo_private=False,
382 repo_name=repo_name,
382 repo_name=repo_name,
383 repo_type=backend.alias,
383 repo_type=backend.alias,
384 repo_description=description,
384 repo_description=description,
385 clone_uri='svn+http://svn.invalid/repo',
385 clone_uri='svn+http://svn.invalid/repo',
386 csrf_token=csrf_token))
386 csrf_token=csrf_token))
387 response.mustcontain('invalid clone url')
387 response.mustcontain('invalid clone url')
388
388
389 def test_create_with_git_suffix(
389 def test_create_with_git_suffix(
390 self, autologin_user, backend, csrf_token):
390 self, autologin_user, backend, csrf_token):
391 repo_name = backend.new_repo_name() + ".git"
391 repo_name = backend.new_repo_name() + ".git"
392 description = 'description for newly created repo'
392 description = 'description for newly created repo'
393 response = self.app.post(
393 response = self.app.post(
394 route_path('repo_create'),
394 route_path('repo_create'),
395 fixture._get_repo_create_params(
395 fixture._get_repo_create_params(
396 repo_private=False,
396 repo_private=False,
397 repo_name=repo_name,
397 repo_name=repo_name,
398 repo_type=backend.alias,
398 repo_type=backend.alias,
399 repo_description=description,
399 repo_description=description,
400 csrf_token=csrf_token))
400 csrf_token=csrf_token))
401 response.mustcontain('Repository name cannot end with .git')
401 response.mustcontain('Repository name cannot end with .git')
402
402
403 def test_default_user_cannot_access_private_repo_in_a_group(
403 def test_default_user_cannot_access_private_repo_in_a_group(
404 self, autologin_user, user_util, backend):
404 self, autologin_user, user_util, backend):
405
405
406 group = user_util.create_repo_group()
406 group = user_util.create_repo_group()
407
407
408 repo = backend.create_repo(
408 repo = backend.create_repo(
409 repo_private=True, repo_group=group, repo_copy_permissions=True)
409 repo_private=True, repo_group=group, repo_copy_permissions=True)
410
410
411 permissions = _get_permission_for_user(
411 permissions = _get_permission_for_user(
412 user='default', repo=repo.repo_name)
412 user='default', repo=repo.repo_name)
413 assert len(permissions) == 1
413 assert len(permissions) == 1
414 assert permissions[0].permission.permission_name == 'repository.none'
414 assert permissions[0].permission.permission_name == 'repository.none'
415 assert permissions[0].repository.private is True
415 assert permissions[0].repository.private is True
416
416
417 def test_create_on_top_level_without_permissions(self, backend):
417 def test_create_on_top_level_without_permissions(self, backend):
418 session = login_user_session(
418 session = login_user_session(
419 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
419 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
420 csrf_token = auth.get_csrf_token(session)
420 csrf_token = auth.get_csrf_token(session)
421
421
422 # revoke
422 # revoke
423 user_model = UserModel()
423 user_model = UserModel()
424 # disable fork and create on default user
424 # disable fork and create on default user
425 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
425 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
426 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
426 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
427 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
427 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
428 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
428 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
429
429
430 # disable on regular user
430 # disable on regular user
431 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
431 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
432 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
432 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
433 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
433 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
434 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
434 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
435 Session().commit()
435 Session().commit()
436
436
437 repo_name = backend.new_repo_name()
437 repo_name = backend.new_repo_name()
438 description = 'description for newly created repo'
438 description = 'description for newly created repo'
439 response = self.app.post(
439 response = self.app.post(
440 route_path('repo_create'),
440 route_path('repo_create'),
441 fixture._get_repo_create_params(
441 fixture._get_repo_create_params(
442 repo_private=False,
442 repo_private=False,
443 repo_name=repo_name,
443 repo_name=repo_name,
444 repo_type=backend.alias,
444 repo_type=backend.alias,
445 repo_description=description,
445 repo_description=description,
446 csrf_token=csrf_token))
446 csrf_token=csrf_token))
447
447
448 response.mustcontain(
448 response.mustcontain(
449 u"You do not have the permission to store repositories in "
449 "You do not have the permission to store repositories in "
450 u"the root location.")
450 "the root location.")
451
451
452 @mock.patch.object(RepoModel, '_create_filesystem_repo', error_function)
452 @mock.patch.object(RepoModel, '_create_filesystem_repo', error_function)
453 def test_create_repo_when_filesystem_op_fails(
453 def test_create_repo_when_filesystem_op_fails(
454 self, autologin_user, backend, csrf_token):
454 self, autologin_user, backend, csrf_token):
455 repo_name = backend.new_repo_name()
455 repo_name = backend.new_repo_name()
456 description = 'description for newly created repo'
456 description = 'description for newly created repo'
457
457
458 response = self.app.post(
458 response = self.app.post(
459 route_path('repo_create'),
459 route_path('repo_create'),
460 fixture._get_repo_create_params(
460 fixture._get_repo_create_params(
461 repo_private=False,
461 repo_private=False,
462 repo_name=repo_name,
462 repo_name=repo_name,
463 repo_type=backend.alias,
463 repo_type=backend.alias,
464 repo_description=description,
464 repo_description=description,
465 csrf_token=csrf_token))
465 csrf_token=csrf_token))
466
466
467 assert_session_flash(
467 assert_session_flash(
468 response, 'Error creating repository %s' % repo_name)
468 response, 'Error creating repository %s' % repo_name)
469 # repo must not be in db
469 # repo must not be in db
470 assert backend.repo is None
470 assert backend.repo is None
471 # repo must not be in filesystem !
471 # repo must not be in filesystem !
472 assert not repo_on_filesystem(repo_name)
472 assert not repo_on_filesystem(repo_name)
473
473
474 def assert_repository_is_created_correctly(self, repo_name, description, backend):
474 def assert_repository_is_created_correctly(self, repo_name, description, backend):
475 url_quoted_repo_name = urllib.parse.quote(repo_name)
475 url_quoted_repo_name = urllib.parse.quote(repo_name)
476
476
477 # run the check page that triggers the flash message
477 # run the check page that triggers the flash message
478 response = self.app.get(
478 response = self.app.get(
479 route_path('repo_creating_check', repo_name=repo_name))
479 route_path('repo_creating_check', repo_name=repo_name))
480 assert response.json == {'result': True}
480 assert response.json == {'result': True}
481
481
482 flash_msg = 'Created repository <a href="/{}">{}</a>'.format(url_quoted_repo_name, repo_name)
482 flash_msg = 'Created repository <a href="/{}">{}</a>'.format(url_quoted_repo_name, repo_name)
483 assert_session_flash(response, flash_msg)
483 assert_session_flash(response, flash_msg)
484
484
485 # test if the repo was created in the database
485 # test if the repo was created in the database
486 new_repo = RepoModel().get_by_repo_name(repo_name)
486 new_repo = RepoModel().get_by_repo_name(repo_name)
487
487
488 assert new_repo.repo_name == repo_name
488 assert new_repo.repo_name == repo_name
489 assert new_repo.description == description
489 assert new_repo.description == description
490
490
491 # test if the repository is visible in the list ?
491 # test if the repository is visible in the list ?
492 response = self.app.get(
492 response = self.app.get(
493 h.route_path('repo_summary', repo_name=repo_name))
493 h.route_path('repo_summary', repo_name=repo_name))
494 response.mustcontain(repo_name)
494 response.mustcontain(repo_name)
495 response.mustcontain(backend.alias)
495 response.mustcontain(backend.alias)
496
496
497 assert repo_on_filesystem(repo_name)
497 assert repo_on_filesystem(repo_name)
@@ -1,678 +1,678 b''
1
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 import rhodecode
23 import rhodecode
24 from rhodecode.apps._base import ADMIN_PREFIX
24 from rhodecode.apps._base import ADMIN_PREFIX
25 from rhodecode.lib.hash_utils import md5_safe
25 from rhodecode.lib.hash_utils import md5_safe
26 from rhodecode.model.db import RhodeCodeUi
26 from rhodecode.model.db import RhodeCodeUi
27 from rhodecode.model.meta import Session
27 from rhodecode.model.meta import Session
28 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
28 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
29 from rhodecode.tests import assert_session_flash
29 from rhodecode.tests import assert_session_flash
30 from rhodecode.tests.routes import route_path
30 from rhodecode.tests.routes import route_path
31
31
32
32
33 UPDATE_DATA_QUALNAME = 'rhodecode.model.update.UpdateModel.get_update_data'
33 UPDATE_DATA_QUALNAME = 'rhodecode.model.update.UpdateModel.get_update_data'
34
34
35
35
36 @pytest.mark.usefixtures('autologin_user', 'app')
36 @pytest.mark.usefixtures('autologin_user', 'app')
37 class TestAdminSettingsController(object):
37 class TestAdminSettingsController(object):
38
38
39 @pytest.mark.parametrize('urlname', [
39 @pytest.mark.parametrize('urlname', [
40 'admin_settings_vcs',
40 'admin_settings_vcs',
41 'admin_settings_mapping',
41 'admin_settings_mapping',
42 'admin_settings_global',
42 'admin_settings_global',
43 'admin_settings_visual',
43 'admin_settings_visual',
44 'admin_settings_email',
44 'admin_settings_email',
45 'admin_settings_hooks',
45 'admin_settings_hooks',
46 'admin_settings_search',
46 'admin_settings_search',
47 ])
47 ])
48 def test_simple_get(self, urlname):
48 def test_simple_get(self, urlname):
49 self.app.get(route_path(urlname))
49 self.app.get(route_path(urlname))
50
50
51 def test_create_custom_hook(self, csrf_token):
51 def test_create_custom_hook(self, csrf_token):
52 response = self.app.post(
52 response = self.app.post(
53 route_path('admin_settings_hooks_update'),
53 route_path('admin_settings_hooks_update'),
54 params={
54 params={
55 'new_hook_ui_key': 'test_hooks_1',
55 'new_hook_ui_key': 'test_hooks_1',
56 'new_hook_ui_value': 'cd /tmp',
56 'new_hook_ui_value': 'cd /tmp',
57 'csrf_token': csrf_token})
57 'csrf_token': csrf_token})
58
58
59 response = response.follow()
59 response = response.follow()
60 response.mustcontain('test_hooks_1')
60 response.mustcontain('test_hooks_1')
61 response.mustcontain('cd /tmp')
61 response.mustcontain('cd /tmp')
62
62
63 def test_create_custom_hook_delete(self, csrf_token):
63 def test_create_custom_hook_delete(self, csrf_token):
64 response = self.app.post(
64 response = self.app.post(
65 route_path('admin_settings_hooks_update'),
65 route_path('admin_settings_hooks_update'),
66 params={
66 params={
67 'new_hook_ui_key': 'test_hooks_2',
67 'new_hook_ui_key': 'test_hooks_2',
68 'new_hook_ui_value': 'cd /tmp2',
68 'new_hook_ui_value': 'cd /tmp2',
69 'csrf_token': csrf_token})
69 'csrf_token': csrf_token})
70
70
71 response = response.follow()
71 response = response.follow()
72 response.mustcontain('test_hooks_2')
72 response.mustcontain('test_hooks_2')
73 response.mustcontain('cd /tmp2')
73 response.mustcontain('cd /tmp2')
74
74
75 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
75 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
76
76
77 # delete
77 # delete
78 self.app.post(
78 self.app.post(
79 route_path('admin_settings_hooks_delete'),
79 route_path('admin_settings_hooks_delete'),
80 params={'hook_id': hook_id, 'csrf_token': csrf_token})
80 params={'hook_id': hook_id, 'csrf_token': csrf_token})
81 response = self.app.get(route_path('admin_settings_hooks'))
81 response = self.app.get(route_path('admin_settings_hooks'))
82 response.mustcontain(no=['test_hooks_2'])
82 response.mustcontain(no=['test_hooks_2'])
83 response.mustcontain(no=['cd /tmp2'])
83 response.mustcontain(no=['cd /tmp2'])
84
84
85
85
86 @pytest.mark.usefixtures('autologin_user', 'app')
86 @pytest.mark.usefixtures('autologin_user', 'app')
87 class TestAdminSettingsGlobal(object):
87 class TestAdminSettingsGlobal(object):
88
88
89 def test_pre_post_code_code_active(self, csrf_token):
89 def test_pre_post_code_code_active(self, csrf_token):
90 pre_code = 'rc-pre-code-187652122'
90 pre_code = 'rc-pre-code-187652122'
91 post_code = 'rc-postcode-98165231'
91 post_code = 'rc-postcode-98165231'
92
92
93 response = self.post_and_verify_settings({
93 response = self.post_and_verify_settings({
94 'rhodecode_pre_code': pre_code,
94 'rhodecode_pre_code': pre_code,
95 'rhodecode_post_code': post_code,
95 'rhodecode_post_code': post_code,
96 'csrf_token': csrf_token,
96 'csrf_token': csrf_token,
97 })
97 })
98
98
99 response = response.follow()
99 response = response.follow()
100 response.mustcontain(pre_code, post_code)
100 response.mustcontain(pre_code, post_code)
101
101
102 def test_pre_post_code_code_inactive(self, csrf_token):
102 def test_pre_post_code_code_inactive(self, csrf_token):
103 pre_code = 'rc-pre-code-187652122'
103 pre_code = 'rc-pre-code-187652122'
104 post_code = 'rc-postcode-98165231'
104 post_code = 'rc-postcode-98165231'
105 response = self.post_and_verify_settings({
105 response = self.post_and_verify_settings({
106 'rhodecode_pre_code': '',
106 'rhodecode_pre_code': '',
107 'rhodecode_post_code': '',
107 'rhodecode_post_code': '',
108 'csrf_token': csrf_token,
108 'csrf_token': csrf_token,
109 })
109 })
110
110
111 response = response.follow()
111 response = response.follow()
112 response.mustcontain(no=[pre_code, post_code])
112 response.mustcontain(no=[pre_code, post_code])
113
113
114 def test_captcha_activate(self, csrf_token):
114 def test_captcha_activate(self, csrf_token):
115 self.post_and_verify_settings({
115 self.post_and_verify_settings({
116 'rhodecode_captcha_private_key': '1234567890',
116 'rhodecode_captcha_private_key': '1234567890',
117 'rhodecode_captcha_public_key': '1234567890',
117 'rhodecode_captcha_public_key': '1234567890',
118 'csrf_token': csrf_token,
118 'csrf_token': csrf_token,
119 })
119 })
120
120
121 response = self.app.get(ADMIN_PREFIX + '/register')
121 response = self.app.get(ADMIN_PREFIX + '/register')
122 response.mustcontain('captcha')
122 response.mustcontain('captcha')
123
123
124 def test_captcha_deactivate(self, csrf_token):
124 def test_captcha_deactivate(self, csrf_token):
125 self.post_and_verify_settings({
125 self.post_and_verify_settings({
126 'rhodecode_captcha_private_key': '',
126 'rhodecode_captcha_private_key': '',
127 'rhodecode_captcha_public_key': '1234567890',
127 'rhodecode_captcha_public_key': '1234567890',
128 'csrf_token': csrf_token,
128 'csrf_token': csrf_token,
129 })
129 })
130
130
131 response = self.app.get(ADMIN_PREFIX + '/register')
131 response = self.app.get(ADMIN_PREFIX + '/register')
132 response.mustcontain(no=['captcha'])
132 response.mustcontain(no=['captcha'])
133
133
134 def test_title_change(self, csrf_token):
134 def test_title_change(self, csrf_token):
135 old_title = 'RhodeCode'
135 old_title = 'RhodeCode'
136
136
137 for new_title in ['Changed', 'Żółwik', old_title]:
137 for new_title in ['Changed', 'Żółwik', old_title]:
138 response = self.post_and_verify_settings({
138 response = self.post_and_verify_settings({
139 'rhodecode_title': new_title,
139 'rhodecode_title': new_title,
140 'csrf_token': csrf_token,
140 'csrf_token': csrf_token,
141 })
141 })
142
142
143 response = response.follow()
143 response = response.follow()
144 response.mustcontain(new_title)
144 response.mustcontain(new_title)
145
145
146 def post_and_verify_settings(self, settings):
146 def post_and_verify_settings(self, settings):
147 old_title = 'RhodeCode'
147 old_title = 'RhodeCode'
148 old_realm = 'RhodeCode authentication'
148 old_realm = 'RhodeCode authentication'
149 params = {
149 params = {
150 'rhodecode_title': old_title,
150 'rhodecode_title': old_title,
151 'rhodecode_realm': old_realm,
151 'rhodecode_realm': old_realm,
152 'rhodecode_pre_code': '',
152 'rhodecode_pre_code': '',
153 'rhodecode_post_code': '',
153 'rhodecode_post_code': '',
154 'rhodecode_captcha_private_key': '',
154 'rhodecode_captcha_private_key': '',
155 'rhodecode_captcha_public_key': '',
155 'rhodecode_captcha_public_key': '',
156 'rhodecode_create_personal_repo_group': False,
156 'rhodecode_create_personal_repo_group': False,
157 'rhodecode_personal_repo_group_pattern': '${username}',
157 'rhodecode_personal_repo_group_pattern': '${username}',
158 }
158 }
159 params.update(settings)
159 params.update(settings)
160 response = self.app.post(
160 response = self.app.post(
161 route_path('admin_settings_global_update'), params=params)
161 route_path('admin_settings_global_update'), params=params)
162
162
163 assert_session_flash(response, 'Updated application settings')
163 assert_session_flash(response, 'Updated application settings')
164
164
165 app_settings = SettingsModel().get_all_settings()
165 app_settings = SettingsModel().get_all_settings()
166 del settings['csrf_token']
166 del settings['csrf_token']
167 for key, value in settings.items():
167 for key, value in settings.items():
168 assert app_settings[key] == value
168 assert app_settings[key] == value
169
169
170 return response
170 return response
171
171
172
172
173 @pytest.mark.usefixtures('autologin_user', 'app')
173 @pytest.mark.usefixtures('autologin_user', 'app')
174 class TestAdminSettingsVcs(object):
174 class TestAdminSettingsVcs(object):
175
175
176 def test_contains_svn_default_patterns(self):
176 def test_contains_svn_default_patterns(self):
177 response = self.app.get(route_path('admin_settings_vcs'))
177 response = self.app.get(route_path('admin_settings_vcs'))
178 expected_patterns = [
178 expected_patterns = [
179 '/trunk',
179 '/trunk',
180 '/branches/*',
180 '/branches/*',
181 '/tags/*',
181 '/tags/*',
182 ]
182 ]
183 for pattern in expected_patterns:
183 for pattern in expected_patterns:
184 response.mustcontain(pattern)
184 response.mustcontain(pattern)
185
185
186 def test_add_new_svn_branch_and_tag_pattern(
186 def test_add_new_svn_branch_and_tag_pattern(
187 self, backend_svn, form_defaults, disable_sql_cache,
187 self, backend_svn, form_defaults, disable_sql_cache,
188 csrf_token):
188 csrf_token):
189 form_defaults.update({
189 form_defaults.update({
190 'new_svn_branch': '/exp/branches/*',
190 'new_svn_branch': '/exp/branches/*',
191 'new_svn_tag': '/important_tags/*',
191 'new_svn_tag': '/important_tags/*',
192 'csrf_token': csrf_token,
192 'csrf_token': csrf_token,
193 })
193 })
194
194
195 response = self.app.post(
195 response = self.app.post(
196 route_path('admin_settings_vcs_update'),
196 route_path('admin_settings_vcs_update'),
197 params=form_defaults, status=302)
197 params=form_defaults, status=302)
198 response = response.follow()
198 response = response.follow()
199
199
200 # Expect to find the new values on the page
200 # Expect to find the new values on the page
201 response.mustcontain('/exp/branches/*')
201 response.mustcontain('/exp/branches/*')
202 response.mustcontain('/important_tags/*')
202 response.mustcontain('/important_tags/*')
203
203
204 # Expect that those patterns are used to match branches and tags now
204 # Expect that those patterns are used to match branches and tags now
205 repo = backend_svn['svn-simple-layout'].scm_instance()
205 repo = backend_svn['svn-simple-layout'].scm_instance()
206 assert 'exp/branches/exp-sphinx-docs' in repo.branches
206 assert 'exp/branches/exp-sphinx-docs' in repo.branches
207 assert 'important_tags/v0.5' in repo.tags
207 assert 'important_tags/v0.5' in repo.tags
208
208
209 def test_add_same_svn_value_twice_shows_an_error_message(
209 def test_add_same_svn_value_twice_shows_an_error_message(
210 self, form_defaults, csrf_token, settings_util):
210 self, form_defaults, csrf_token, settings_util):
211 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
211 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
212 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
212 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
213
213
214 response = self.app.post(
214 response = self.app.post(
215 route_path('admin_settings_vcs_update'),
215 route_path('admin_settings_vcs_update'),
216 params={
216 params={
217 'paths_root_path': form_defaults['paths_root_path'],
217 'paths_root_path': form_defaults['paths_root_path'],
218 'new_svn_branch': '/test',
218 'new_svn_branch': '/test',
219 'new_svn_tag': '/test',
219 'new_svn_tag': '/test',
220 'csrf_token': csrf_token,
220 'csrf_token': csrf_token,
221 },
221 },
222 status=200)
222 status=200)
223
223
224 response.mustcontain("Pattern already exists")
224 response.mustcontain("Pattern already exists")
225 response.mustcontain("Some form inputs contain invalid data.")
225 response.mustcontain("Some form inputs contain invalid data.")
226
226
227 @pytest.mark.parametrize('section', [
227 @pytest.mark.parametrize('section', [
228 'vcs_svn_branch',
228 'vcs_svn_branch',
229 'vcs_svn_tag',
229 'vcs_svn_tag',
230 ])
230 ])
231 def test_delete_svn_patterns(
231 def test_delete_svn_patterns(
232 self, section, csrf_token, settings_util):
232 self, section, csrf_token, settings_util):
233 setting = settings_util.create_rhodecode_ui(
233 setting = settings_util.create_rhodecode_ui(
234 section, '/test_delete', cleanup=False)
234 section, '/test_delete', cleanup=False)
235
235
236 self.app.post(
236 self.app.post(
237 route_path('admin_settings_vcs_svn_pattern_delete'),
237 route_path('admin_settings_vcs_svn_pattern_delete'),
238 params={
238 params={
239 'delete_svn_pattern': setting.ui_id,
239 'delete_svn_pattern': setting.ui_id,
240 'csrf_token': csrf_token},
240 'csrf_token': csrf_token},
241 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
241 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
242
242
243 @pytest.mark.parametrize('section', [
243 @pytest.mark.parametrize('section', [
244 'vcs_svn_branch',
244 'vcs_svn_branch',
245 'vcs_svn_tag',
245 'vcs_svn_tag',
246 ])
246 ])
247 def test_delete_svn_patterns_raises_404_when_no_xhr(
247 def test_delete_svn_patterns_raises_404_when_no_xhr(
248 self, section, csrf_token, settings_util):
248 self, section, csrf_token, settings_util):
249 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
249 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
250
250
251 self.app.post(
251 self.app.post(
252 route_path('admin_settings_vcs_svn_pattern_delete'),
252 route_path('admin_settings_vcs_svn_pattern_delete'),
253 params={
253 params={
254 'delete_svn_pattern': setting.ui_id,
254 'delete_svn_pattern': setting.ui_id,
255 'csrf_token': csrf_token},
255 'csrf_token': csrf_token},
256 status=404)
256 status=404)
257
257
258 def test_extensions_hgevolve(self, form_defaults, csrf_token):
258 def test_extensions_hgevolve(self, form_defaults, csrf_token):
259 form_defaults.update({
259 form_defaults.update({
260 'csrf_token': csrf_token,
260 'csrf_token': csrf_token,
261 'extensions_evolve': 'True',
261 'extensions_evolve': 'True',
262 })
262 })
263 response = self.app.post(
263 response = self.app.post(
264 route_path('admin_settings_vcs_update'),
264 route_path('admin_settings_vcs_update'),
265 params=form_defaults,
265 params=form_defaults,
266 status=302)
266 status=302)
267
267
268 response = response.follow()
268 response = response.follow()
269 extensions_input = (
269 extensions_input = (
270 '<input id="extensions_evolve" '
270 '<input id="extensions_evolve" '
271 'name="extensions_evolve" type="checkbox" '
271 'name="extensions_evolve" type="checkbox" '
272 'value="True" checked="checked" />')
272 'value="True" checked="checked" />')
273 response.mustcontain(extensions_input)
273 response.mustcontain(extensions_input)
274
274
275 def test_has_a_section_for_pull_request_settings(self):
275 def test_has_a_section_for_pull_request_settings(self):
276 response = self.app.get(route_path('admin_settings_vcs'))
276 response = self.app.get(route_path('admin_settings_vcs'))
277 response.mustcontain('Pull Request Settings')
277 response.mustcontain('Pull Request Settings')
278
278
279 def test_has_an_input_for_invalidation_of_inline_comments(self):
279 def test_has_an_input_for_invalidation_of_inline_comments(self):
280 response = self.app.get(route_path('admin_settings_vcs'))
280 response = self.app.get(route_path('admin_settings_vcs'))
281 assert_response = response.assert_response()
281 assert_response = response.assert_response()
282 assert_response.one_element_exists(
282 assert_response.one_element_exists(
283 '[name=rhodecode_use_outdated_comments]')
283 '[name=rhodecode_use_outdated_comments]')
284
284
285 @pytest.mark.parametrize('new_value', [True, False])
285 @pytest.mark.parametrize('new_value', [True, False])
286 def test_allows_to_change_invalidation_of_inline_comments(
286 def test_allows_to_change_invalidation_of_inline_comments(
287 self, form_defaults, csrf_token, new_value):
287 self, form_defaults, csrf_token, new_value):
288 setting_key = 'use_outdated_comments'
288 setting_key = 'use_outdated_comments'
289 setting = SettingsModel().create_or_update_setting(
289 setting = SettingsModel().create_or_update_setting(
290 setting_key, not new_value, 'bool')
290 setting_key, not new_value, 'bool')
291 Session().add(setting)
291 Session().add(setting)
292 Session().commit()
292 Session().commit()
293
293
294 form_defaults.update({
294 form_defaults.update({
295 'csrf_token': csrf_token,
295 'csrf_token': csrf_token,
296 'rhodecode_use_outdated_comments': str(new_value),
296 'rhodecode_use_outdated_comments': str(new_value),
297 })
297 })
298 response = self.app.post(
298 response = self.app.post(
299 route_path('admin_settings_vcs_update'),
299 route_path('admin_settings_vcs_update'),
300 params=form_defaults,
300 params=form_defaults,
301 status=302)
301 status=302)
302 response = response.follow()
302 response = response.follow()
303 setting = SettingsModel().get_setting_by_name(setting_key)
303 setting = SettingsModel().get_setting_by_name(setting_key)
304 assert setting.app_settings_value is new_value
304 assert setting.app_settings_value is new_value
305
305
306 @pytest.mark.parametrize('new_value', [True, False])
306 @pytest.mark.parametrize('new_value', [True, False])
307 def test_allows_to_change_hg_rebase_merge_strategy(
307 def test_allows_to_change_hg_rebase_merge_strategy(
308 self, form_defaults, csrf_token, new_value):
308 self, form_defaults, csrf_token, new_value):
309 setting_key = 'hg_use_rebase_for_merging'
309 setting_key = 'hg_use_rebase_for_merging'
310
310
311 form_defaults.update({
311 form_defaults.update({
312 'csrf_token': csrf_token,
312 'csrf_token': csrf_token,
313 'rhodecode_' + setting_key: str(new_value),
313 'rhodecode_' + setting_key: str(new_value),
314 })
314 })
315
315
316 with mock.patch.dict(
316 with mock.patch.dict(
317 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
317 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
318 self.app.post(
318 self.app.post(
319 route_path('admin_settings_vcs_update'),
319 route_path('admin_settings_vcs_update'),
320 params=form_defaults,
320 params=form_defaults,
321 status=302)
321 status=302)
322
322
323 setting = SettingsModel().get_setting_by_name(setting_key)
323 setting = SettingsModel().get_setting_by_name(setting_key)
324 assert setting.app_settings_value is new_value
324 assert setting.app_settings_value is new_value
325
325
326 @pytest.fixture()
326 @pytest.fixture()
327 def disable_sql_cache(self, request):
327 def disable_sql_cache(self, request):
328 # patch _do_orm_execute so it returns None similar like if we don't use a cached query
328 # patch _do_orm_execute so it returns None similar like if we don't use a cached query
329 patcher = mock.patch(
329 patcher = mock.patch(
330 'rhodecode.lib.caching_query.ORMCache._do_orm_execute', return_value=None)
330 'rhodecode.lib.caching_query.ORMCache._do_orm_execute', return_value=None)
331 request.addfinalizer(patcher.stop)
331 request.addfinalizer(patcher.stop)
332 patcher.start()
332 patcher.start()
333
333
334 @pytest.fixture()
334 @pytest.fixture()
335 def form_defaults(self):
335 def form_defaults(self):
336 from rhodecode.apps.admin.views.settings import AdminSettingsView
336 from rhodecode.apps.admin.views.settings import AdminSettingsView
337 return AdminSettingsView._form_defaults()
337 return AdminSettingsView._form_defaults()
338
338
339 # TODO: johbo: What we really want is to checkpoint before a test run and
339 # TODO: johbo: What we really want is to checkpoint before a test run and
340 # reset the session afterwards.
340 # reset the session afterwards.
341 @pytest.fixture(scope='class', autouse=True)
341 @pytest.fixture(scope='class', autouse=True)
342 def cleanup_settings(self, request, baseapp):
342 def cleanup_settings(self, request, baseapp):
343 ui_id = RhodeCodeUi.ui_id
343 ui_id = RhodeCodeUi.ui_id
344 original_ids = [r.ui_id for r in RhodeCodeUi.query().with_entities(ui_id)]
344 original_ids = [r.ui_id for r in RhodeCodeUi.query().with_entities(ui_id)]
345
345
346 @request.addfinalizer
346 @request.addfinalizer
347 def cleanup():
347 def cleanup():
348 RhodeCodeUi.query().filter(
348 RhodeCodeUi.query().filter(
349 ui_id.notin_(original_ids)).delete(False)
349 ui_id.notin_(original_ids)).delete(False)
350
350
351
351
352 @pytest.mark.usefixtures('autologin_user', 'app')
352 @pytest.mark.usefixtures('autologin_user', 'app')
353 class TestLabsSettings(object):
353 class TestLabsSettings(object):
354 def test_get_settings_page_disabled(self):
354 def test_get_settings_page_disabled(self):
355 with mock.patch.dict(
355 with mock.patch.dict(
356 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
356 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
357
357
358 response = self.app.get(
358 response = self.app.get(
359 route_path('admin_settings_labs'), status=302)
359 route_path('admin_settings_labs'), status=302)
360
360
361 assert response.location.endswith(route_path('admin_settings'))
361 assert response.location.endswith(route_path('admin_settings'))
362
362
363 def test_get_settings_page_enabled(self):
363 def test_get_settings_page_enabled(self):
364 from rhodecode.apps.admin.views import settings
364 from rhodecode.apps.admin.views import settings
365 lab_settings = [
365 lab_settings = [
366 settings.LabSetting(
366 settings.LabSetting(
367 key='rhodecode_bool',
367 key='rhodecode_bool',
368 type='bool',
368 type='bool',
369 group='bool group',
369 group='bool group',
370 label='bool label',
370 label='bool label',
371 help='bool help'
371 help='bool help'
372 ),
372 ),
373 settings.LabSetting(
373 settings.LabSetting(
374 key='rhodecode_text',
374 key='rhodecode_text',
375 type='unicode',
375 type='unicode',
376 group='text group',
376 group='text group',
377 label='text label',
377 label='text label',
378 help='text help'
378 help='text help'
379 ),
379 ),
380 ]
380 ]
381 with mock.patch.dict(rhodecode.CONFIG,
381 with mock.patch.dict(rhodecode.CONFIG,
382 {'labs_settings_active': 'true'}):
382 {'labs_settings_active': 'true'}):
383 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
383 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
384 response = self.app.get(route_path('admin_settings_labs'))
384 response = self.app.get(route_path('admin_settings_labs'))
385
385
386 assert '<label>bool group:</label>' in response
386 assert '<label>bool group:</label>' in response
387 assert '<label for="rhodecode_bool">bool label</label>' in response
387 assert '<label for="rhodecode_bool">bool label</label>' in response
388 assert '<p class="help-block">bool help</p>' in response
388 assert '<p class="help-block">bool help</p>' in response
389 assert 'name="rhodecode_bool" type="checkbox"' in response
389 assert 'name="rhodecode_bool" type="checkbox"' in response
390
390
391 assert '<label>text group:</label>' in response
391 assert '<label>text group:</label>' in response
392 assert '<label for="rhodecode_text">text label</label>' in response
392 assert '<label for="rhodecode_text">text label</label>' in response
393 assert '<p class="help-block">text help</p>' in response
393 assert '<p class="help-block">text help</p>' in response
394 assert 'name="rhodecode_text" size="60" type="text"' in response
394 assert 'name="rhodecode_text" size="60" type="text"' in response
395
395
396
396
397 @pytest.mark.usefixtures('app')
397 @pytest.mark.usefixtures('app')
398 class TestOpenSourceLicenses(object):
398 class TestOpenSourceLicenses(object):
399
399
400 def test_records_are_displayed(self, autologin_user):
400 def test_records_are_displayed(self, autologin_user):
401 sample_licenses = [
401 sample_licenses = [
402 {
402 {
403 "license": [
403 "license": [
404 {
404 {
405 "fullName": "BSD 4-clause \"Original\" or \"Old\" License",
405 "fullName": "BSD 4-clause \"Original\" or \"Old\" License",
406 "shortName": "bsdOriginal",
406 "shortName": "bsdOriginal",
407 "spdxId": "BSD-4-Clause",
407 "spdxId": "BSD-4-Clause",
408 "url": "http://spdx.org/licenses/BSD-4-Clause.html"
408 "url": "http://spdx.org/licenses/BSD-4-Clause.html"
409 }
409 }
410 ],
410 ],
411 "name": "python2.7-coverage-3.7.1"
411 "name": "python2.7-coverage-3.7.1"
412 },
412 },
413 {
413 {
414 "license": [
414 "license": [
415 {
415 {
416 "fullName": "MIT License",
416 "fullName": "MIT License",
417 "shortName": "mit",
417 "shortName": "mit",
418 "spdxId": "MIT",
418 "spdxId": "MIT",
419 "url": "http://spdx.org/licenses/MIT.html"
419 "url": "http://spdx.org/licenses/MIT.html"
420 }
420 }
421 ],
421 ],
422 "name": "python2.7-bootstrapped-pip-9.0.1"
422 "name": "python2.7-bootstrapped-pip-9.0.1"
423 },
423 },
424 ]
424 ]
425 read_licenses_patch = mock.patch(
425 read_licenses_patch = mock.patch(
426 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
426 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
427 return_value=sample_licenses)
427 return_value=sample_licenses)
428 with read_licenses_patch:
428 with read_licenses_patch:
429 response = self.app.get(
429 response = self.app.get(
430 route_path('admin_settings_open_source'), status=200)
430 route_path('admin_settings_open_source'), status=200)
431
431
432 assert_response = response.assert_response()
432 assert_response = response.assert_response()
433 assert_response.element_contains(
433 assert_response.element_contains(
434 '.panel-heading', 'Licenses of Third Party Packages')
434 '.panel-heading', 'Licenses of Third Party Packages')
435 for license_data in sample_licenses:
435 for license_data in sample_licenses:
436 response.mustcontain(license_data["license"][0]["spdxId"])
436 response.mustcontain(license_data["license"][0]["spdxId"])
437 assert_response.element_contains('.panel-body', license_data["name"])
437 assert_response.element_contains('.panel-body', license_data["name"])
438
438
439 def test_records_can_be_read(self, autologin_user):
439 def test_records_can_be_read(self, autologin_user):
440 response = self.app.get(
440 response = self.app.get(
441 route_path('admin_settings_open_source'), status=200)
441 route_path('admin_settings_open_source'), status=200)
442 assert_response = response.assert_response()
442 assert_response = response.assert_response()
443 assert_response.element_contains(
443 assert_response.element_contains(
444 '.panel-heading', 'Licenses of Third Party Packages')
444 '.panel-heading', 'Licenses of Third Party Packages')
445
445
446 def test_forbidden_when_normal_user(self, autologin_regular_user):
446 def test_forbidden_when_normal_user(self, autologin_regular_user):
447 self.app.get(
447 self.app.get(
448 route_path('admin_settings_open_source'), status=404)
448 route_path('admin_settings_open_source'), status=404)
449
449
450
450
451 @pytest.mark.usefixtures('app')
451 @pytest.mark.usefixtures('app')
452 class TestUserSessions(object):
452 class TestUserSessions(object):
453
453
454 def test_forbidden_when_normal_user(self, autologin_regular_user):
454 def test_forbidden_when_normal_user(self, autologin_regular_user):
455 self.app.get(route_path('admin_settings_sessions'), status=404)
455 self.app.get(route_path('admin_settings_sessions'), status=404)
456
456
457 def test_show_sessions_page(self, autologin_user):
457 def test_show_sessions_page(self, autologin_user):
458 response = self.app.get(route_path('admin_settings_sessions'), status=200)
458 response = self.app.get(route_path('admin_settings_sessions'), status=200)
459 response.mustcontain('file')
459 response.mustcontain('file')
460
460
461 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
461 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
462
462
463 post_data = {
463 post_data = {
464 'csrf_token': csrf_token,
464 'csrf_token': csrf_token,
465 'expire_days': '60'
465 'expire_days': '60'
466 }
466 }
467 response = self.app.post(
467 response = self.app.post(
468 route_path('admin_settings_sessions_cleanup'), params=post_data,
468 route_path('admin_settings_sessions_cleanup'), params=post_data,
469 status=302)
469 status=302)
470 assert_session_flash(response, 'Cleaned up old sessions')
470 assert_session_flash(response, 'Cleaned up old sessions')
471
471
472
472
473 @pytest.mark.usefixtures('app')
473 @pytest.mark.usefixtures('app')
474 class TestAdminSystemInfo(object):
474 class TestAdminSystemInfo(object):
475
475
476 def test_forbidden_when_normal_user(self, autologin_regular_user):
476 def test_forbidden_when_normal_user(self, autologin_regular_user):
477 self.app.get(route_path('admin_settings_system'), status=404)
477 self.app.get(route_path('admin_settings_system'), status=404)
478
478
479 def test_system_info_page(self, autologin_user):
479 def test_system_info_page(self, autologin_user):
480 response = self.app.get(route_path('admin_settings_system'))
480 response = self.app.get(route_path('admin_settings_system'))
481 response.mustcontain('RhodeCode Community Edition, version {}'.format(
481 response.mustcontain('RhodeCode Community Edition, version {}'.format(
482 rhodecode.__version__))
482 rhodecode.__version__))
483
483
484 def test_system_update_new_version(self, autologin_user):
484 def test_system_update_new_version(self, autologin_user):
485 update_data = {
485 update_data = {
486 'versions': [
486 'versions': [
487 {
487 {
488 'version': '100.3.1415926535',
488 'version': '100.0.0',
489 'general': 'The latest version we are ever going to ship'
489 'general': 'The latest version we are ever going to ship'
490 },
490 },
491 {
491 {
492 'version': '0.0.0',
492 'version': '0.0.0',
493 'general': 'The first version we ever shipped'
493 'general': 'The first version we ever shipped'
494 }
494 }
495 ]
495 ]
496 }
496 }
497 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
497 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
498 response = self.app.get(route_path('admin_settings_system_update'))
498 response = self.app.get(route_path('admin_settings_system_update'))
499 response.mustcontain('A <b>new version</b> is available')
499 response.mustcontain('A <b>new version</b> is available')
500
500
501 def test_system_update_nothing_new(self, autologin_user):
501 def test_system_update_nothing_new(self, autologin_user):
502 update_data = {
502 update_data = {
503 'versions': [
503 'versions': [
504 {
504 {
505 'version': '0.0.0',
505 'version': '4.0.0',
506 'general': 'The first version we ever shipped'
506 'general': 'The first version we ever shipped'
507 }
507 }
508 ]
508 ]
509 }
509 }
510 text = f"Your current version, {rhodecode.__version__}, is up-to-date as it is equal to or newer than the latest available version, 4.0.0."
510 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
511 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
511 response = self.app.get(route_path('admin_settings_system_update'))
512 response = self.app.get(route_path('admin_settings_system_update'))
512 response.mustcontain(
513 response.mustcontain(text)
513 'This instance is already running the <b>latest</b> stable version')
514
514
515 def test_system_update_bad_response(self, autologin_user):
515 def test_system_update_bad_response(self, autologin_user):
516 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
516 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
517 response = self.app.get(route_path('admin_settings_system_update'))
517 response = self.app.get(route_path('admin_settings_system_update'))
518 response.mustcontain(
518 response.mustcontain(
519 'Bad data sent from update server')
519 'Bad data sent from update server')
520
520
521
521
522 @pytest.mark.usefixtures("app")
522 @pytest.mark.usefixtures("app")
523 class TestAdminSettingsIssueTracker(object):
523 class TestAdminSettingsIssueTracker(object):
524 RC_PREFIX = 'rhodecode_'
524 RC_PREFIX = 'rhodecode_'
525 SHORT_PATTERN_KEY = 'issuetracker_pat_'
525 SHORT_PATTERN_KEY = 'issuetracker_pat_'
526 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
526 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
527 DESC_KEY = RC_PREFIX + 'issuetracker_desc_'
527 DESC_KEY = RC_PREFIX + 'issuetracker_desc_'
528
528
529 def test_issuetracker_index(self, autologin_user):
529 def test_issuetracker_index(self, autologin_user):
530 response = self.app.get(route_path('admin_settings_issuetracker'))
530 response = self.app.get(route_path('admin_settings_issuetracker'))
531 assert response.status_code == 200
531 assert response.status_code == 200
532
532
533 def test_add_empty_issuetracker_pattern(
533 def test_add_empty_issuetracker_pattern(
534 self, request, autologin_user, csrf_token):
534 self, request, autologin_user, csrf_token):
535 post_url = route_path('admin_settings_issuetracker_update')
535 post_url = route_path('admin_settings_issuetracker_update')
536 post_data = {
536 post_data = {
537 'csrf_token': csrf_token
537 'csrf_token': csrf_token
538 }
538 }
539 self.app.post(post_url, post_data, status=302)
539 self.app.post(post_url, post_data, status=302)
540
540
541 def test_add_issuetracker_pattern(
541 def test_add_issuetracker_pattern(
542 self, request, autologin_user, csrf_token):
542 self, request, autologin_user, csrf_token):
543 pattern = 'issuetracker_pat'
543 pattern = 'issuetracker_pat'
544 another_pattern = pattern+'1'
544 another_pattern = pattern+'1'
545 post_url = route_path('admin_settings_issuetracker_update')
545 post_url = route_path('admin_settings_issuetracker_update')
546 post_data = {
546 post_data = {
547 'new_pattern_pattern_0': pattern,
547 'new_pattern_pattern_0': pattern,
548 'new_pattern_url_0': 'http://url',
548 'new_pattern_url_0': 'http://url',
549 'new_pattern_prefix_0': 'prefix',
549 'new_pattern_prefix_0': 'prefix',
550 'new_pattern_description_0': 'description',
550 'new_pattern_description_0': 'description',
551 'new_pattern_pattern_1': another_pattern,
551 'new_pattern_pattern_1': another_pattern,
552 'new_pattern_url_1': 'https://url1',
552 'new_pattern_url_1': 'https://url1',
553 'new_pattern_prefix_1': 'prefix1',
553 'new_pattern_prefix_1': 'prefix1',
554 'new_pattern_description_1': 'description1',
554 'new_pattern_description_1': 'description1',
555 'csrf_token': csrf_token
555 'csrf_token': csrf_token
556 }
556 }
557 self.app.post(post_url, post_data, status=302)
557 self.app.post(post_url, post_data, status=302)
558 settings = SettingsModel().get_all_settings()
558 settings = SettingsModel().get_all_settings()
559 self.uid = md5_safe(pattern)
559 self.uid = md5_safe(pattern)
560 assert settings[self.PATTERN_KEY+self.uid] == pattern
560 assert settings[self.PATTERN_KEY+self.uid] == pattern
561 self.another_uid = md5_safe(another_pattern)
561 self.another_uid = md5_safe(another_pattern)
562 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
562 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
563
563
564 @request.addfinalizer
564 @request.addfinalizer
565 def cleanup():
565 def cleanup():
566 defaults = SettingsModel().get_all_settings()
566 defaults = SettingsModel().get_all_settings()
567
567
568 entries = [name for name in defaults if (
568 entries = [name for name in defaults if (
569 (self.uid in name) or (self.another_uid in name))]
569 (self.uid in name) or (self.another_uid in name))]
570 start = len(self.RC_PREFIX)
570 start = len(self.RC_PREFIX)
571 for del_key in entries:
571 for del_key in entries:
572 # TODO: anderson: get_by_name needs name without prefix
572 # TODO: anderson: get_by_name needs name without prefix
573 entry = SettingsModel().get_setting_by_name(del_key[start:])
573 entry = SettingsModel().get_setting_by_name(del_key[start:])
574 Session().delete(entry)
574 Session().delete(entry)
575
575
576 Session().commit()
576 Session().commit()
577
577
578 def test_edit_issuetracker_pattern(
578 def test_edit_issuetracker_pattern(
579 self, autologin_user, backend, csrf_token, request):
579 self, autologin_user, backend, csrf_token, request):
580
580
581 old_pattern = 'issuetracker_pat1'
581 old_pattern = 'issuetracker_pat1'
582 old_uid = md5_safe(old_pattern)
582 old_uid = md5_safe(old_pattern)
583
583
584 post_url = route_path('admin_settings_issuetracker_update')
584 post_url = route_path('admin_settings_issuetracker_update')
585 post_data = {
585 post_data = {
586 'new_pattern_pattern_0': old_pattern,
586 'new_pattern_pattern_0': old_pattern,
587 'new_pattern_url_0': 'http://url',
587 'new_pattern_url_0': 'http://url',
588 'new_pattern_prefix_0': 'prefix',
588 'new_pattern_prefix_0': 'prefix',
589 'new_pattern_description_0': 'description',
589 'new_pattern_description_0': 'description',
590
590
591 'csrf_token': csrf_token
591 'csrf_token': csrf_token
592 }
592 }
593 self.app.post(post_url, post_data, status=302)
593 self.app.post(post_url, post_data, status=302)
594
594
595 new_pattern = 'issuetracker_pat1_edited'
595 new_pattern = 'issuetracker_pat1_edited'
596 self.new_uid = md5_safe(new_pattern)
596 self.new_uid = md5_safe(new_pattern)
597
597
598 post_url = route_path('admin_settings_issuetracker_update')
598 post_url = route_path('admin_settings_issuetracker_update')
599 post_data = {
599 post_data = {
600 'new_pattern_pattern_{}'.format(old_uid): new_pattern,
600 'new_pattern_pattern_{}'.format(old_uid): new_pattern,
601 'new_pattern_url_{}'.format(old_uid): 'https://url_edited',
601 'new_pattern_url_{}'.format(old_uid): 'https://url_edited',
602 'new_pattern_prefix_{}'.format(old_uid): 'prefix_edited',
602 'new_pattern_prefix_{}'.format(old_uid): 'prefix_edited',
603 'new_pattern_description_{}'.format(old_uid): 'description_edited',
603 'new_pattern_description_{}'.format(old_uid): 'description_edited',
604 'uid': old_uid,
604 'uid': old_uid,
605 'csrf_token': csrf_token
605 'csrf_token': csrf_token
606 }
606 }
607 self.app.post(post_url, post_data, status=302)
607 self.app.post(post_url, post_data, status=302)
608
608
609 settings = SettingsModel().get_all_settings()
609 settings = SettingsModel().get_all_settings()
610 assert settings[self.PATTERN_KEY+self.new_uid] == new_pattern
610 assert settings[self.PATTERN_KEY+self.new_uid] == new_pattern
611 assert settings[self.DESC_KEY + self.new_uid] == 'description_edited'
611 assert settings[self.DESC_KEY + self.new_uid] == 'description_edited'
612 assert self.PATTERN_KEY+old_uid not in settings
612 assert self.PATTERN_KEY+old_uid not in settings
613
613
614 @request.addfinalizer
614 @request.addfinalizer
615 def cleanup():
615 def cleanup():
616 IssueTrackerSettingsModel().delete_entries(old_uid)
616 IssueTrackerSettingsModel().delete_entries(old_uid)
617 IssueTrackerSettingsModel().delete_entries(self.new_uid)
617 IssueTrackerSettingsModel().delete_entries(self.new_uid)
618
618
619 def test_replace_issuetracker_pattern_description(
619 def test_replace_issuetracker_pattern_description(
620 self, autologin_user, csrf_token, request, settings_util):
620 self, autologin_user, csrf_token, request, settings_util):
621 prefix = 'issuetracker'
621 prefix = 'issuetracker'
622 pattern = 'issuetracker_pat'
622 pattern = 'issuetracker_pat'
623 self.uid = md5_safe(pattern)
623 self.uid = md5_safe(pattern)
624 pattern_key = '_'.join([prefix, 'pat', self.uid])
624 pattern_key = '_'.join([prefix, 'pat', self.uid])
625 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
625 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
626 desc_key = '_'.join([prefix, 'desc', self.uid])
626 desc_key = '_'.join([prefix, 'desc', self.uid])
627 rc_desc_key = '_'.join(['rhodecode', desc_key])
627 rc_desc_key = '_'.join(['rhodecode', desc_key])
628 new_description = 'new_description'
628 new_description = 'new_description'
629
629
630 settings_util.create_rhodecode_setting(
630 settings_util.create_rhodecode_setting(
631 pattern_key, pattern, 'unicode', cleanup=False)
631 pattern_key, pattern, 'unicode', cleanup=False)
632 settings_util.create_rhodecode_setting(
632 settings_util.create_rhodecode_setting(
633 desc_key, 'old description', 'unicode', cleanup=False)
633 desc_key, 'old description', 'unicode', cleanup=False)
634
634
635 post_url = route_path('admin_settings_issuetracker_update')
635 post_url = route_path('admin_settings_issuetracker_update')
636 post_data = {
636 post_data = {
637 'new_pattern_pattern_0': pattern,
637 'new_pattern_pattern_0': pattern,
638 'new_pattern_url_0': 'https://url',
638 'new_pattern_url_0': 'https://url',
639 'new_pattern_prefix_0': 'prefix',
639 'new_pattern_prefix_0': 'prefix',
640 'new_pattern_description_0': new_description,
640 'new_pattern_description_0': new_description,
641 'uid': self.uid,
641 'uid': self.uid,
642 'csrf_token': csrf_token
642 'csrf_token': csrf_token
643 }
643 }
644 self.app.post(post_url, post_data, status=302)
644 self.app.post(post_url, post_data, status=302)
645 settings = SettingsModel().get_all_settings()
645 settings = SettingsModel().get_all_settings()
646 assert settings[rc_pattern_key] == pattern
646 assert settings[rc_pattern_key] == pattern
647 assert settings[rc_desc_key] == new_description
647 assert settings[rc_desc_key] == new_description
648
648
649 @request.addfinalizer
649 @request.addfinalizer
650 def cleanup():
650 def cleanup():
651 IssueTrackerSettingsModel().delete_entries(self.uid)
651 IssueTrackerSettingsModel().delete_entries(self.uid)
652
652
653 def test_delete_issuetracker_pattern(
653 def test_delete_issuetracker_pattern(
654 self, autologin_user, backend, csrf_token, settings_util, xhr_header):
654 self, autologin_user, backend, csrf_token, settings_util, xhr_header):
655
655
656 old_pattern = 'issuetracker_pat_deleted'
656 old_pattern = 'issuetracker_pat_deleted'
657 old_uid = md5_safe(old_pattern)
657 old_uid = md5_safe(old_pattern)
658
658
659 post_url = route_path('admin_settings_issuetracker_update')
659 post_url = route_path('admin_settings_issuetracker_update')
660 post_data = {
660 post_data = {
661 'new_pattern_pattern_0': old_pattern,
661 'new_pattern_pattern_0': old_pattern,
662 'new_pattern_url_0': 'http://url',
662 'new_pattern_url_0': 'http://url',
663 'new_pattern_prefix_0': 'prefix',
663 'new_pattern_prefix_0': 'prefix',
664 'new_pattern_description_0': 'description',
664 'new_pattern_description_0': 'description',
665
665
666 'csrf_token': csrf_token
666 'csrf_token': csrf_token
667 }
667 }
668 self.app.post(post_url, post_data, status=302)
668 self.app.post(post_url, post_data, status=302)
669
669
670 post_url = route_path('admin_settings_issuetracker_delete')
670 post_url = route_path('admin_settings_issuetracker_delete')
671 post_data = {
671 post_data = {
672 'uid': old_uid,
672 'uid': old_uid,
673 'csrf_token': csrf_token
673 'csrf_token': csrf_token
674 }
674 }
675 self.app.post(post_url, post_data, extra_environ=xhr_header, status=200)
675 self.app.post(post_url, post_data, extra_environ=xhr_header, status=200)
676 settings = SettingsModel().get_all_settings()
676 settings = SettingsModel().get_all_settings()
677 assert self.PATTERN_KEY+old_uid not in settings
677 assert self.PATTERN_KEY+old_uid not in settings
678 assert self.DESC_KEY + old_uid not in settings
678 assert self.DESC_KEY + old_uid not in settings
@@ -1,477 +1,477 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import re
19 import re
20 import logging
20 import logging
21 import formencode
21 import formencode
22 import formencode.htmlfill
22 import formencode.htmlfill
23 import datetime
23 import datetime
24 from pyramid.interfaces import IRoutesMapper
24 from pyramid.interfaces import IRoutesMapper
25
25
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode.apps._base import BaseAppView, DataGridAppView
30 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
31 from rhodecode.apps.ssh_support.events import SshKeyFileChangeEvent
32 from rhodecode import events
32 from rhodecode import events
33
33
34 from rhodecode.lib import helpers as h
34 from rhodecode.lib import helpers as h
35 from rhodecode.lib.auth import (
35 from rhodecode.lib.auth import (
36 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
36 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
37 from rhodecode.lib.utils2 import aslist, safe_str
37 from rhodecode.lib.utils2 import aslist, safe_str
38 from rhodecode.model.db import (
38 from rhodecode.model.db import (
39 or_, coalesce, User, UserIpMap, UserSshKeys)
39 or_, coalesce, User, UserIpMap, UserSshKeys)
40 from rhodecode.model.forms import (
40 from rhodecode.model.forms import (
41 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
41 ApplicationPermissionsForm, ObjectPermissionsForm, UserPermissionsForm)
42 from rhodecode.model.meta import Session
42 from rhodecode.model.meta import Session
43 from rhodecode.model.permission import PermissionModel
43 from rhodecode.model.permission import PermissionModel
44 from rhodecode.model.settings import SettingsModel
44 from rhodecode.model.settings import SettingsModel
45
45
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class AdminPermissionsView(BaseAppView, DataGridAppView):
50 class AdminPermissionsView(BaseAppView, DataGridAppView):
51 def load_default_context(self):
51 def load_default_context(self):
52 c = self._get_local_tmpl_context()
52 c = self._get_local_tmpl_context()
53 PermissionModel().set_global_permission_choices(
53 PermissionModel().set_global_permission_choices(
54 c, gettext_translator=self.request.translate)
54 c, gettext_translator=self.request.translate)
55 return c
55 return c
56
56
57 @LoginRequired()
57 @LoginRequired()
58 @HasPermissionAllDecorator('hg.admin')
58 @HasPermissionAllDecorator('hg.admin')
59 def permissions_application(self):
59 def permissions_application(self):
60 c = self.load_default_context()
60 c = self.load_default_context()
61 c.active = 'application'
61 c.active = 'application'
62
62
63 c.user = User.get_default_user(refresh=True)
63 c.user = User.get_default_user(refresh=True)
64
64
65 app_settings = c.rc_config
65 app_settings = c.rc_config
66
66
67 defaults = {
67 defaults = {
68 'anonymous': c.user.active,
68 'anonymous': c.user.active,
69 'default_register_message': app_settings.get(
69 'default_register_message': app_settings.get(
70 'rhodecode_register_message')
70 'rhodecode_register_message')
71 }
71 }
72 defaults.update(c.user.get_default_perms())
72 defaults.update(c.user.get_default_perms())
73
73
74 data = render('rhodecode:templates/admin/permissions/permissions.mako',
74 data = render('rhodecode:templates/admin/permissions/permissions.mako',
75 self._get_template_context(c), self.request)
75 self._get_template_context(c), self.request)
76 html = formencode.htmlfill.render(
76 html = formencode.htmlfill.render(
77 data,
77 data,
78 defaults=defaults,
78 defaults=defaults,
79 encoding="UTF-8",
79 encoding="UTF-8",
80 force_defaults=False
80 force_defaults=False
81 )
81 )
82 return Response(html)
82 return Response(html)
83
83
84 @LoginRequired()
84 @LoginRequired()
85 @HasPermissionAllDecorator('hg.admin')
85 @HasPermissionAllDecorator('hg.admin')
86 @CSRFRequired()
86 @CSRFRequired()
87 def permissions_application_update(self):
87 def permissions_application_update(self):
88 _ = self.request.translate
88 _ = self.request.translate
89 c = self.load_default_context()
89 c = self.load_default_context()
90 c.active = 'application'
90 c.active = 'application'
91
91
92 _form = ApplicationPermissionsForm(
92 _form = ApplicationPermissionsForm(
93 self.request.translate,
93 self.request.translate,
94 [x[0] for x in c.register_choices],
94 [x[0] for x in c.register_choices],
95 [x[0] for x in c.password_reset_choices],
95 [x[0] for x in c.password_reset_choices],
96 [x[0] for x in c.extern_activate_choices])()
96 [x[0] for x in c.extern_activate_choices])()
97
97
98 try:
98 try:
99 form_result = _form.to_python(dict(self.request.POST))
99 form_result = _form.to_python(dict(self.request.POST))
100 form_result.update({'perm_user_name': User.DEFAULT_USER})
100 form_result.update({'perm_user_name': User.DEFAULT_USER})
101 PermissionModel().update_application_permissions(form_result)
101 PermissionModel().update_application_permissions(form_result)
102
102
103 settings = [
103 settings = [
104 ('register_message', 'default_register_message'),
104 ('register_message', 'default_register_message'),
105 ]
105 ]
106 for setting, form_key in settings:
106 for setting, form_key in settings:
107 sett = SettingsModel().create_or_update_setting(
107 sett = SettingsModel().create_or_update_setting(
108 setting, form_result[form_key])
108 setting, form_result[form_key])
109 Session().add(sett)
109 Session().add(sett)
110
110
111 Session().commit()
111 Session().commit()
112 h.flash(_('Application permissions updated successfully'),
112 h.flash(_('Application permissions updated successfully'),
113 category='success')
113 category='success')
114
114
115 except formencode.Invalid as errors:
115 except formencode.Invalid as errors:
116 defaults = errors.value
116 defaults = errors.value
117
117
118 data = render(
118 data = render(
119 'rhodecode:templates/admin/permissions/permissions.mako',
119 'rhodecode:templates/admin/permissions/permissions.mako',
120 self._get_template_context(c), self.request)
120 self._get_template_context(c), self.request)
121 html = formencode.htmlfill.render(
121 html = formencode.htmlfill.render(
122 data,
122 data,
123 defaults=defaults,
123 defaults=defaults,
124 errors=errors.unpack_errors() or {},
124 errors=errors.unpack_errors() or {},
125 prefix_error=False,
125 prefix_error=False,
126 encoding="UTF-8",
126 encoding="UTF-8",
127 force_defaults=False
127 force_defaults=False
128 )
128 )
129 return Response(html)
129 return Response(html)
130
130
131 except Exception:
131 except Exception:
132 log.exception("Exception during update of permissions")
132 log.exception("Exception during update of permissions")
133 h.flash(_('Error occurred during update of permissions'),
133 h.flash(_('Error occurred during update of permissions'),
134 category='error')
134 category='error')
135
135
136 affected_user_ids = [User.get_default_user_id()]
136 affected_user_ids = [User.get_default_user_id()]
137 PermissionModel().trigger_permission_flush(affected_user_ids)
137 PermissionModel().trigger_permission_flush(affected_user_ids)
138
138
139 raise HTTPFound(h.route_path('admin_permissions_application'))
139 raise HTTPFound(h.route_path('admin_permissions_application'))
140
140
141 @LoginRequired()
141 @LoginRequired()
142 @HasPermissionAllDecorator('hg.admin')
142 @HasPermissionAllDecorator('hg.admin')
143 def permissions_objects(self):
143 def permissions_objects(self):
144 c = self.load_default_context()
144 c = self.load_default_context()
145 c.active = 'objects'
145 c.active = 'objects'
146
146
147 c.user = User.get_default_user(refresh=True)
147 c.user = User.get_default_user(refresh=True)
148 defaults = {}
148 defaults = {}
149 defaults.update(c.user.get_default_perms())
149 defaults.update(c.user.get_default_perms())
150
150
151 data = render(
151 data = render(
152 'rhodecode:templates/admin/permissions/permissions.mako',
152 'rhodecode:templates/admin/permissions/permissions.mako',
153 self._get_template_context(c), self.request)
153 self._get_template_context(c), self.request)
154 html = formencode.htmlfill.render(
154 html = formencode.htmlfill.render(
155 data,
155 data,
156 defaults=defaults,
156 defaults=defaults,
157 encoding="UTF-8",
157 encoding="UTF-8",
158 force_defaults=False
158 force_defaults=False
159 )
159 )
160 return Response(html)
160 return Response(html)
161
161
162 @LoginRequired()
162 @LoginRequired()
163 @HasPermissionAllDecorator('hg.admin')
163 @HasPermissionAllDecorator('hg.admin')
164 @CSRFRequired()
164 @CSRFRequired()
165 def permissions_objects_update(self):
165 def permissions_objects_update(self):
166 _ = self.request.translate
166 _ = self.request.translate
167 c = self.load_default_context()
167 c = self.load_default_context()
168 c.active = 'objects'
168 c.active = 'objects'
169
169
170 _form = ObjectPermissionsForm(
170 _form = ObjectPermissionsForm(
171 self.request.translate,
171 self.request.translate,
172 [x[0] for x in c.repo_perms_choices],
172 [x[0] for x in c.repo_perms_choices],
173 [x[0] for x in c.group_perms_choices],
173 [x[0] for x in c.group_perms_choices],
174 [x[0] for x in c.user_group_perms_choices],
174 [x[0] for x in c.user_group_perms_choices],
175 )()
175 )()
176
176
177 try:
177 try:
178 form_result = _form.to_python(dict(self.request.POST))
178 form_result = _form.to_python(dict(self.request.POST))
179 form_result.update({'perm_user_name': User.DEFAULT_USER})
179 form_result.update({'perm_user_name': User.DEFAULT_USER})
180 PermissionModel().update_object_permissions(form_result)
180 PermissionModel().update_object_permissions(form_result)
181
181
182 Session().commit()
182 Session().commit()
183 h.flash(_('Object permissions updated successfully'),
183 h.flash(_('Object permissions updated successfully'),
184 category='success')
184 category='success')
185
185
186 except formencode.Invalid as errors:
186 except formencode.Invalid as errors:
187 defaults = errors.value
187 defaults = errors.value
188
188
189 data = render(
189 data = render(
190 'rhodecode:templates/admin/permissions/permissions.mako',
190 'rhodecode:templates/admin/permissions/permissions.mako',
191 self._get_template_context(c), self.request)
191 self._get_template_context(c), self.request)
192 html = formencode.htmlfill.render(
192 html = formencode.htmlfill.render(
193 data,
193 data,
194 defaults=defaults,
194 defaults=defaults,
195 errors=errors.unpack_errors() or {},
195 errors=errors.unpack_errors() or {},
196 prefix_error=False,
196 prefix_error=False,
197 encoding="UTF-8",
197 encoding="UTF-8",
198 force_defaults=False
198 force_defaults=False
199 )
199 )
200 return Response(html)
200 return Response(html)
201 except Exception:
201 except Exception:
202 log.exception("Exception during update of permissions")
202 log.exception("Exception during update of permissions")
203 h.flash(_('Error occurred during update of permissions'),
203 h.flash(_('Error occurred during update of permissions'),
204 category='error')
204 category='error')
205
205
206 affected_user_ids = [User.get_default_user_id()]
206 affected_user_ids = [User.get_default_user_id()]
207 PermissionModel().trigger_permission_flush(affected_user_ids)
207 PermissionModel().trigger_permission_flush(affected_user_ids)
208
208
209 raise HTTPFound(h.route_path('admin_permissions_object'))
209 raise HTTPFound(h.route_path('admin_permissions_object'))
210
210
211 @LoginRequired()
211 @LoginRequired()
212 @HasPermissionAllDecorator('hg.admin')
212 @HasPermissionAllDecorator('hg.admin')
213 def permissions_branch(self):
213 def permissions_branch(self):
214 c = self.load_default_context()
214 c = self.load_default_context()
215 c.active = 'branch'
215 c.active = 'branch'
216
216
217 c.user = User.get_default_user(refresh=True)
217 c.user = User.get_default_user(refresh=True)
218 defaults = {}
218 defaults = {}
219 defaults.update(c.user.get_default_perms())
219 defaults.update(c.user.get_default_perms())
220
220
221 data = render(
221 data = render(
222 'rhodecode:templates/admin/permissions/permissions.mako',
222 'rhodecode:templates/admin/permissions/permissions.mako',
223 self._get_template_context(c), self.request)
223 self._get_template_context(c), self.request)
224 html = formencode.htmlfill.render(
224 html = formencode.htmlfill.render(
225 data,
225 data,
226 defaults=defaults,
226 defaults=defaults,
227 encoding="UTF-8",
227 encoding="UTF-8",
228 force_defaults=False
228 force_defaults=False
229 )
229 )
230 return Response(html)
230 return Response(html)
231
231
232 @LoginRequired()
232 @LoginRequired()
233 @HasPermissionAllDecorator('hg.admin')
233 @HasPermissionAllDecorator('hg.admin')
234 def permissions_global(self):
234 def permissions_global(self):
235 c = self.load_default_context()
235 c = self.load_default_context()
236 c.active = 'global'
236 c.active = 'global'
237
237
238 c.user = User.get_default_user(refresh=True)
238 c.user = User.get_default_user(refresh=True)
239 defaults = {}
239 defaults = {}
240 defaults.update(c.user.get_default_perms())
240 defaults.update(c.user.get_default_perms())
241
241
242 data = render(
242 data = render(
243 'rhodecode:templates/admin/permissions/permissions.mako',
243 'rhodecode:templates/admin/permissions/permissions.mako',
244 self._get_template_context(c), self.request)
244 self._get_template_context(c), self.request)
245 html = formencode.htmlfill.render(
245 html = formencode.htmlfill.render(
246 data,
246 data,
247 defaults=defaults,
247 defaults=defaults,
248 encoding="UTF-8",
248 encoding="UTF-8",
249 force_defaults=False
249 force_defaults=False
250 )
250 )
251 return Response(html)
251 return Response(html)
252
252
253 @LoginRequired()
253 @LoginRequired()
254 @HasPermissionAllDecorator('hg.admin')
254 @HasPermissionAllDecorator('hg.admin')
255 @CSRFRequired()
255 @CSRFRequired()
256 def permissions_global_update(self):
256 def permissions_global_update(self):
257 _ = self.request.translate
257 _ = self.request.translate
258 c = self.load_default_context()
258 c = self.load_default_context()
259 c.active = 'global'
259 c.active = 'global'
260
260
261 _form = UserPermissionsForm(
261 _form = UserPermissionsForm(
262 self.request.translate,
262 self.request.translate,
263 [x[0] for x in c.repo_create_choices],
263 [x[0] for x in c.repo_create_choices],
264 [x[0] for x in c.repo_create_on_write_choices],
264 [x[0] for x in c.repo_create_on_write_choices],
265 [x[0] for x in c.repo_group_create_choices],
265 [x[0] for x in c.repo_group_create_choices],
266 [x[0] for x in c.user_group_create_choices],
266 [x[0] for x in c.user_group_create_choices],
267 [x[0] for x in c.fork_choices],
267 [x[0] for x in c.fork_choices],
268 [x[0] for x in c.inherit_default_permission_choices])()
268 [x[0] for x in c.inherit_default_permission_choices])()
269
269
270 try:
270 try:
271 form_result = _form.to_python(dict(self.request.POST))
271 form_result = _form.to_python(dict(self.request.POST))
272 form_result.update({'perm_user_name': User.DEFAULT_USER})
272 form_result.update({'perm_user_name': User.DEFAULT_USER})
273 PermissionModel().update_user_permissions(form_result)
273 PermissionModel().update_user_permissions(form_result)
274
274
275 Session().commit()
275 Session().commit()
276 h.flash(_('Global permissions updated successfully'),
276 h.flash(_('Global permissions updated successfully'),
277 category='success')
277 category='success')
278
278
279 except formencode.Invalid as errors:
279 except formencode.Invalid as errors:
280 defaults = errors.value
280 defaults = errors.value
281
281
282 data = render(
282 data = render(
283 'rhodecode:templates/admin/permissions/permissions.mako',
283 'rhodecode:templates/admin/permissions/permissions.mako',
284 self._get_template_context(c), self.request)
284 self._get_template_context(c), self.request)
285 html = formencode.htmlfill.render(
285 html = formencode.htmlfill.render(
286 data,
286 data,
287 defaults=defaults,
287 defaults=defaults,
288 errors=errors.unpack_errors() or {},
288 errors=errors.unpack_errors() or {},
289 prefix_error=False,
289 prefix_error=False,
290 encoding="UTF-8",
290 encoding="UTF-8",
291 force_defaults=False
291 force_defaults=False
292 )
292 )
293 return Response(html)
293 return Response(html)
294 except Exception:
294 except Exception:
295 log.exception("Exception during update of permissions")
295 log.exception("Exception during update of permissions")
296 h.flash(_('Error occurred during update of permissions'),
296 h.flash(_('Error occurred during update of permissions'),
297 category='error')
297 category='error')
298
298
299 affected_user_ids = [User.get_default_user_id()]
299 affected_user_ids = [User.get_default_user_id()]
300 PermissionModel().trigger_permission_flush(affected_user_ids)
300 PermissionModel().trigger_permission_flush(affected_user_ids)
301
301
302 raise HTTPFound(h.route_path('admin_permissions_global'))
302 raise HTTPFound(h.route_path('admin_permissions_global'))
303
303
304 @LoginRequired()
304 @LoginRequired()
305 @HasPermissionAllDecorator('hg.admin')
305 @HasPermissionAllDecorator('hg.admin')
306 def permissions_ips(self):
306 def permissions_ips(self):
307 c = self.load_default_context()
307 c = self.load_default_context()
308 c.active = 'ips'
308 c.active = 'ips'
309
309
310 c.user = User.get_default_user(refresh=True)
310 c.user = User.get_default_user(refresh=True)
311 c.user_ip_map = (
311 c.user_ip_map = (
312 UserIpMap.query().filter(UserIpMap.user == c.user).all())
312 UserIpMap.query().filter(UserIpMap.user == c.user).all())
313
313
314 return self._get_template_context(c)
314 return self._get_template_context(c)
315
315
316 @LoginRequired()
316 @LoginRequired()
317 @HasPermissionAllDecorator('hg.admin')
317 @HasPermissionAllDecorator('hg.admin')
318 def permissions_overview(self):
318 def permissions_overview(self):
319 c = self.load_default_context()
319 c = self.load_default_context()
320 c.active = 'perms'
320 c.active = 'perms'
321
321
322 c.user = User.get_default_user(refresh=True)
322 c.user = User.get_default_user(refresh=True)
323 c.perm_user = c.user.AuthUser()
323 c.perm_user = c.user.AuthUser()
324 return self._get_template_context(c)
324 return self._get_template_context(c)
325
325
326 @LoginRequired()
326 @LoginRequired()
327 @HasPermissionAllDecorator('hg.admin')
327 @HasPermissionAllDecorator('hg.admin')
328 def auth_token_access(self):
328 def auth_token_access(self):
329 from rhodecode import CONFIG
329 from rhodecode import CONFIG
330
330
331 c = self.load_default_context()
331 c = self.load_default_context()
332 c.active = 'auth_token_access'
332 c.active = 'auth_token_access'
333
333
334 c.user = User.get_default_user(refresh=True)
334 c.user = User.get_default_user(refresh=True)
335 c.perm_user = c.user.AuthUser()
335 c.perm_user = c.user.AuthUser()
336
336
337 mapper = self.request.registry.queryUtility(IRoutesMapper)
337 mapper = self.request.registry.queryUtility(IRoutesMapper)
338 c.view_data = []
338 c.view_data = []
339
339
340 _argument_prog = re.compile(r'\{(.*?)\}|:\((.*)\)')
340 _argument_prog = re.compile(r'\{(.*?)\}|:\((.*)\)')
341 introspector = self.request.registry.introspector
341 introspector = self.request.registry.introspector
342
342
343 view_intr = {}
343 view_intr = {}
344 for view_data in introspector.get_category('views'):
344 for view_data in introspector.get_category('views'):
345 intr = view_data['introspectable']
345 intr = view_data['introspectable']
346
346
347 if 'route_name' in intr and intr['attr']:
347 if 'route_name' in intr and intr['attr']:
348 view_intr[intr['route_name']] = '{}:{}'.format(
348 view_intr[intr['route_name']] = '{}:{}'.format(
349 str(intr['derived_callable'].__name__), intr['attr']
349 str(intr['derived_callable'].__name__), intr['attr']
350 )
350 )
351
351
352 c.whitelist_key = 'api_access_controllers_whitelist'
352 c.whitelist_key = 'api_access_controllers_whitelist'
353 c.whitelist_file = CONFIG.get('__file__')
353 c.whitelist_file = CONFIG.get('__file__')
354 whitelist_views = aslist(
354 whitelist_views = aslist(
355 CONFIG.get(c.whitelist_key), sep=',')
355 CONFIG.get(c.whitelist_key), sep=',')
356
356
357 for route_info in mapper.get_routes():
357 for route_info in mapper.get_routes():
358 if not route_info.name.startswith('__'):
358 if not route_info.name.startswith('__'):
359 routepath = route_info.pattern
359 routepath = route_info.pattern
360
360
361 def replace(matchobj):
361 def replace(matchobj):
362 if matchobj.group(1):
362 if matchobj.group(1):
363 return "{%s}" % matchobj.group(1).split(':')[0]
363 return "{%s}" % matchobj.group(1).split(':')[0]
364 else:
364 else:
365 return "{%s}" % matchobj.group(2)
365 return "{%s}" % matchobj.group(2)
366
366
367 routepath = _argument_prog.sub(replace, routepath)
367 routepath = _argument_prog.sub(replace, routepath)
368
368
369 if not routepath.startswith('/'):
369 if not routepath.startswith('/'):
370 routepath = '/' + routepath
370 routepath = '/' + routepath
371
371
372 view_fqn = view_intr.get(route_info.name, 'NOT AVAILABLE')
372 view_fqn = view_intr.get(route_info.name, 'NOT AVAILABLE')
373 active = view_fqn in whitelist_views
373 active = view_fqn in whitelist_views
374 c.view_data.append((route_info.name, view_fqn, routepath, active))
374 c.view_data.append((route_info.name, view_fqn, routepath, active))
375
375
376 c.whitelist_views = whitelist_views
376 c.whitelist_views = whitelist_views
377 return self._get_template_context(c)
377 return self._get_template_context(c)
378
378
379 def ssh_enabled(self):
379 def ssh_enabled(self):
380 return self.request.registry.settings.get(
380 return self.request.registry.settings.get(
381 'ssh.generate_authorized_keyfile')
381 'ssh.generate_authorized_keyfile')
382
382
383 @LoginRequired()
383 @LoginRequired()
384 @HasPermissionAllDecorator('hg.admin')
384 @HasPermissionAllDecorator('hg.admin')
385 def ssh_keys(self):
385 def ssh_keys(self):
386 c = self.load_default_context()
386 c = self.load_default_context()
387 c.active = 'ssh_keys'
387 c.active = 'ssh_keys'
388 c.ssh_enabled = self.ssh_enabled()
388 c.ssh_enabled = self.ssh_enabled()
389 return self._get_template_context(c)
389 return self._get_template_context(c)
390
390
391 @LoginRequired()
391 @LoginRequired()
392 @HasPermissionAllDecorator('hg.admin')
392 @HasPermissionAllDecorator('hg.admin')
393 def ssh_keys_data(self):
393 def ssh_keys_data(self):
394 _ = self.request.translate
394 _ = self.request.translate
395 self.load_default_context()
395 self.load_default_context()
396 column_map = {
396 column_map = {
397 'fingerprint': 'ssh_key_fingerprint',
397 'fingerprint': 'ssh_key_fingerprint',
398 'username': User.username
398 'username': User.username
399 }
399 }
400 draw, start, limit = self._extract_chunk(self.request)
400 draw, start, limit = self._extract_chunk(self.request)
401 search_q, order_by, order_dir = self._extract_ordering(
401 search_q, order_by, order_dir = self._extract_ordering(
402 self.request, column_map=column_map)
402 self.request, column_map=column_map)
403
403
404 ssh_keys_data_total_count = UserSshKeys.query()\
404 ssh_keys_data_total_count = UserSshKeys.query()\
405 .count()
405 .count()
406
406
407 # json generate
407 # json generate
408 base_q = UserSshKeys.query().join(UserSshKeys.user)
408 base_q = UserSshKeys.query().join(UserSshKeys.user)
409
409
410 if search_q:
410 if search_q:
411 like_expression = f'%{safe_str(search_q)}%'
411 like_expression = f'%{safe_str(search_q)}%'
412 base_q = base_q.filter(or_(
412 base_q = base_q.filter(or_(
413 User.username.ilike(like_expression),
413 User.username.ilike(like_expression),
414 UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
414 UserSshKeys.ssh_key_fingerprint.ilike(like_expression),
415 ))
415 ))
416
416
417 users_data_total_filtered_count = base_q.count()
417 users_data_total_filtered_count = base_q.count()
418
418
419 sort_col = self._get_order_col(order_by, UserSshKeys)
419 sort_col = self._get_order_col(order_by, UserSshKeys)
420 if sort_col:
420 if sort_col:
421 if order_dir == 'asc':
421 if order_dir == 'asc':
422 # handle null values properly to order by NULL last
422 # handle null values properly to order by NULL last
423 if order_by in ['created_on']:
423 if order_by in ['created_on']:
424 sort_col = coalesce(sort_col, datetime.date.max)
424 sort_col = coalesce(sort_col, datetime.date.max)
425 sort_col = sort_col.asc()
425 sort_col = sort_col.asc()
426 else:
426 else:
427 # handle null values properly to order by NULL last
427 # handle null values properly to order by NULL last
428 if order_by in ['created_on']:
428 if order_by in ['created_on']:
429 sort_col = coalesce(sort_col, datetime.date.min)
429 sort_col = coalesce(sort_col, datetime.date.min)
430 sort_col = sort_col.desc()
430 sort_col = sort_col.desc()
431
431
432 base_q = base_q.order_by(sort_col)
432 base_q = base_q.order_by(sort_col)
433 base_q = base_q.offset(start).limit(limit)
433 base_q = base_q.offset(start).limit(limit)
434
434
435 ssh_keys = base_q.all()
435 ssh_keys = base_q.all()
436
436
437 ssh_keys_data = []
437 ssh_keys_data = []
438 for ssh_key in ssh_keys:
438 for ssh_key in ssh_keys:
439 ssh_keys_data.append({
439 ssh_keys_data.append({
440 "username": h.gravatar_with_user(self.request, ssh_key.user.username),
440 "username": h.gravatar_with_user(self.request, ssh_key.user.username),
441 "fingerprint": ssh_key.ssh_key_fingerprint,
441 "fingerprint": ssh_key.ssh_key_fingerprint,
442 "description": ssh_key.description,
442 "description": ssh_key.description,
443 "created_on": h.format_date(ssh_key.created_on),
443 "created_on": h.format_date(ssh_key.created_on),
444 "accessed_on": h.format_date(ssh_key.accessed_on),
444 "accessed_on": h.format_date(ssh_key.accessed_on),
445 "action": h.link_to(
445 "action": h.link_to(
446 _('Edit'), h.route_path('edit_user_ssh_keys',
446 _('Edit'), h.route_path('edit_user_ssh_keys',
447 user_id=ssh_key.user.user_id))
447 user_id=ssh_key.user.user_id))
448 })
448 })
449
449
450 data = ({
450 data = ({
451 'draw': draw,
451 'draw': draw,
452 'data': ssh_keys_data,
452 'data': ssh_keys_data,
453 'recordsTotal': ssh_keys_data_total_count,
453 'recordsTotal': ssh_keys_data_total_count,
454 'recordsFiltered': users_data_total_filtered_count,
454 'recordsFiltered': users_data_total_filtered_count,
455 })
455 })
456
456
457 return data
457 return data
458
458
459 @LoginRequired()
459 @LoginRequired()
460 @HasPermissionAllDecorator('hg.admin')
460 @HasPermissionAllDecorator('hg.admin')
461 @CSRFRequired()
461 @CSRFRequired()
462 def ssh_keys_update(self):
462 def ssh_keys_update(self):
463 _ = self.request.translate
463 _ = self.request.translate
464 self.load_default_context()
464 self.load_default_context()
465
465
466 ssh_enabled = self.ssh_enabled()
466 ssh_enabled = self.ssh_enabled()
467 key_file = self.request.registry.settings.get(
467 key_file = self.request.registry.settings.get(
468 'ssh.authorized_keys_file_path')
468 'ssh.authorized_keys_file_path')
469 if ssh_enabled:
469 if ssh_enabled:
470 events.trigger(SshKeyFileChangeEvent(), self.request.registry)
470 events.trigger(SshKeyFileChangeEvent(), self.request.registry)
471 h.flash(_('Updated SSH keys file: {}').format(key_file),
471 h.flash(_('Updated SSH keys file: {}').format(key_file),
472 category='success')
472 category='success')
473 else:
473 else:
474 h.flash(_('SSH key support is disabled in .ini file'),
474 h.flash(_('SSH key support is disabled in .ini file'),
475 category='warning')
475 category='warning')
476
476
477 raise HTTPFound(h.route_path('admin_permissions_ssh_keys'))
477 raise HTTPFound(h.route_path('admin_permissions_ssh_keys'))
@@ -1,714 +1,708 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19
19
20 import logging
20 import logging
21 import collections
21 import collections
22
22
23 import datetime
23 import datetime
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26
26
27 import rhodecode
27 import rhodecode
28
28
29 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
29 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 from rhodecode.apps._base import BaseAppView
33 from rhodecode.apps._base import BaseAppView
34 from rhodecode.apps._base.navigation import navigation_list
34 from rhodecode.apps._base.navigation import navigation_list
35 from rhodecode.apps.svn_support.config_keys import generate_config
35 from rhodecode.apps.svn_support import config_keys
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib.auth import (
37 from rhodecode.lib.auth import (
38 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
38 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
39 from rhodecode.lib.celerylib import tasks, run_task
39 from rhodecode.lib.celerylib import tasks, run_task
40 from rhodecode.lib.str_utils import safe_str
40 from rhodecode.lib.str_utils import safe_str
41 from rhodecode.lib.utils import repo2db_mapper
41 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_repo_store_path
42 from rhodecode.lib.utils2 import str2bool, AttributeDict
42 from rhodecode.lib.utils2 import str2bool, AttributeDict
43 from rhodecode.lib.index import searcher_from_config
43 from rhodecode.lib.index import searcher_from_config
44
44
45 from rhodecode.model.db import RhodeCodeUi, Repository
45 from rhodecode.model.db import RhodeCodeUi, Repository
46 from rhodecode.model.forms import (ApplicationSettingsForm,
46 from rhodecode.model.forms import (ApplicationSettingsForm,
47 ApplicationUiSettingsForm, ApplicationVisualisationForm,
47 ApplicationUiSettingsForm, ApplicationVisualisationForm,
48 LabsSettingsForm, IssueTrackerPatternsForm)
48 LabsSettingsForm, IssueTrackerPatternsForm)
49 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.permission import PermissionModel
50 from rhodecode.model.repo_group import RepoGroupModel
50 from rhodecode.model.repo_group import RepoGroupModel
51
51
52 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.notification import EmailNotificationModel
53 from rhodecode.model.notification import EmailNotificationModel
54 from rhodecode.model.meta import Session
54 from rhodecode.model.meta import Session
55 from rhodecode.model.settings import (
55 from rhodecode.model.settings import (
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
57 SettingsModel)
57 SettingsModel)
58
58
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class AdminSettingsView(BaseAppView):
63 class AdminSettingsView(BaseAppView):
64
64
65 def load_default_context(self):
65 def load_default_context(self):
66 c = self._get_local_tmpl_context()
66 c = self._get_local_tmpl_context()
67 c.labs_active = str2bool(
67 c.labs_active = str2bool(
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
69 c.navlist = navigation_list(self.request)
69 c.navlist = navigation_list(self.request)
70 return c
70 return c
71
71
72 @classmethod
72 @classmethod
73 def _get_ui_settings(cls):
73 def _get_ui_settings(cls):
74 ret = RhodeCodeUi.query().all()
74 ret = RhodeCodeUi.query().all()
75
75
76 if not ret:
76 if not ret:
77 raise Exception('Could not get application ui settings !')
77 raise Exception('Could not get application ui settings !')
78 settings = {}
78 settings = {}
79 for each in ret:
79 for each in ret:
80 k = each.ui_key
80 k = each.ui_key
81 v = each.ui_value
81 v = each.ui_value
82 if k == '/':
82 if k == '/':
83 k = 'root_path'
83 k = 'root_path'
84
84
85 if k in ['push_ssl', 'publish', 'enabled']:
85 if k in ['push_ssl', 'publish', 'enabled']:
86 v = str2bool(v)
86 v = str2bool(v)
87
87
88 if k.find('.') != -1:
88 if k.find('.') != -1:
89 k = k.replace('.', '_')
89 k = k.replace('.', '_')
90
90
91 if each.ui_section in ['hooks', 'extensions']:
91 if each.ui_section in ['hooks', 'extensions']:
92 v = each.ui_active
92 v = each.ui_active
93
93
94 settings[each.ui_section + '_' + k] = v
94 settings[each.ui_section + '_' + k] = v
95 return settings
95 return settings
96
96
97 @classmethod
97 @classmethod
98 def _form_defaults(cls):
98 def _form_defaults(cls):
99 defaults = SettingsModel().get_all_settings()
99 defaults = SettingsModel().get_all_settings()
100 defaults.update(cls._get_ui_settings())
100 defaults.update(cls._get_ui_settings())
101
101
102 defaults.update({
102 defaults.update({
103 'new_svn_branch': '',
103 'new_svn_branch': '',
104 'new_svn_tag': '',
104 'new_svn_tag': '',
105 })
105 })
106 return defaults
106 return defaults
107
107
108 @LoginRequired()
108 @LoginRequired()
109 @HasPermissionAllDecorator('hg.admin')
109 @HasPermissionAllDecorator('hg.admin')
110 def settings_vcs(self):
110 def settings_vcs(self):
111 c = self.load_default_context()
111 c = self.load_default_context()
112 c.active = 'vcs'
112 c.active = 'vcs'
113 model = VcsSettingsModel()
113 model = VcsSettingsModel()
114 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
114 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
115 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
115 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
116
116 c.svn_generate_config = rhodecode.ConfigGet().get_bool(config_keys.generate_config)
117 settings = self.request.registry.settings
117 c.svn_config_path = rhodecode.ConfigGet().get_str(config_keys.config_file_path)
118 c.svn_proxy_generate_config = settings[generate_config]
119
120 defaults = self._form_defaults()
118 defaults = self._form_defaults()
121
119
122 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
120 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
123
121
124 data = render('rhodecode:templates/admin/settings/settings.mako',
122 data = render('rhodecode:templates/admin/settings/settings.mako',
125 self._get_template_context(c), self.request)
123 self._get_template_context(c), self.request)
126 html = formencode.htmlfill.render(
124 html = formencode.htmlfill.render(
127 data,
125 data,
128 defaults=defaults,
126 defaults=defaults,
129 encoding="UTF-8",
127 encoding="UTF-8",
130 force_defaults=False
128 force_defaults=False
131 )
129 )
132 return Response(html)
130 return Response(html)
133
131
134 @LoginRequired()
132 @LoginRequired()
135 @HasPermissionAllDecorator('hg.admin')
133 @HasPermissionAllDecorator('hg.admin')
136 @CSRFRequired()
134 @CSRFRequired()
137 def settings_vcs_update(self):
135 def settings_vcs_update(self):
138 _ = self.request.translate
136 _ = self.request.translate
139 c = self.load_default_context()
137 c = self.load_default_context()
140 c.active = 'vcs'
138 c.active = 'vcs'
141
139
142 model = VcsSettingsModel()
140 model = VcsSettingsModel()
143 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
141 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
144 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
142 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
145
143
146 settings = self.request.registry.settings
144 c.svn_generate_config = rhodecode.ConfigGet().get_bool(config_keys.generate_config)
147 c.svn_proxy_generate_config = settings[generate_config]
145 c.svn_config_path = rhodecode.ConfigGet().get_str(config_keys.config_file_path)
148
149 application_form = ApplicationUiSettingsForm(self.request.translate)()
146 application_form = ApplicationUiSettingsForm(self.request.translate)()
150
147
151 try:
148 try:
152 form_result = application_form.to_python(dict(self.request.POST))
149 form_result = application_form.to_python(dict(self.request.POST))
153 except formencode.Invalid as errors:
150 except formencode.Invalid as errors:
154 h.flash(
151 h.flash(
155 _("Some form inputs contain invalid data."),
152 _("Some form inputs contain invalid data."),
156 category='error')
153 category='error')
157 data = render('rhodecode:templates/admin/settings/settings.mako',
154 data = render('rhodecode:templates/admin/settings/settings.mako',
158 self._get_template_context(c), self.request)
155 self._get_template_context(c), self.request)
159 html = formencode.htmlfill.render(
156 html = formencode.htmlfill.render(
160 data,
157 data,
161 defaults=errors.value,
158 defaults=errors.value,
162 errors=errors.unpack_errors() or {},
159 errors=errors.unpack_errors() or {},
163 prefix_error=False,
160 prefix_error=False,
164 encoding="UTF-8",
161 encoding="UTF-8",
165 force_defaults=False
162 force_defaults=False
166 )
163 )
167 return Response(html)
164 return Response(html)
168
165
169 try:
166 try:
170 if c.visual.allow_repo_location_change:
171 model.update_global_path_setting(form_result['paths_root_path'])
172
173 model.update_global_ssl_setting(form_result['web_push_ssl'])
167 model.update_global_ssl_setting(form_result['web_push_ssl'])
174 model.update_global_hook_settings(form_result)
168 model.update_global_hook_settings(form_result)
175
169
176 model.create_or_update_global_svn_settings(form_result)
170 model.create_or_update_global_svn_settings(form_result)
177 model.create_or_update_global_hg_settings(form_result)
171 model.create_or_update_global_hg_settings(form_result)
178 model.create_or_update_global_git_settings(form_result)
172 model.create_or_update_global_git_settings(form_result)
179 model.create_or_update_global_pr_settings(form_result)
173 model.create_or_update_global_pr_settings(form_result)
180 except Exception:
174 except Exception:
181 log.exception("Exception while updating settings")
175 log.exception("Exception while updating settings")
182 h.flash(_('Error occurred during updating '
176 h.flash(_('Error occurred during updating '
183 'application settings'), category='error')
177 'application settings'), category='error')
184 else:
178 else:
185 Session().commit()
179 Session().commit()
186 h.flash(_('Updated VCS settings'), category='success')
180 h.flash(_('Updated VCS settings'), category='success')
187 raise HTTPFound(h.route_path('admin_settings_vcs'))
181 raise HTTPFound(h.route_path('admin_settings_vcs'))
188
182
189 data = render('rhodecode:templates/admin/settings/settings.mako',
183 data = render('rhodecode:templates/admin/settings/settings.mako',
190 self._get_template_context(c), self.request)
184 self._get_template_context(c), self.request)
191 html = formencode.htmlfill.render(
185 html = formencode.htmlfill.render(
192 data,
186 data,
193 defaults=self._form_defaults(),
187 defaults=self._form_defaults(),
194 encoding="UTF-8",
188 encoding="UTF-8",
195 force_defaults=False
189 force_defaults=False
196 )
190 )
197 return Response(html)
191 return Response(html)
198
192
199 @LoginRequired()
193 @LoginRequired()
200 @HasPermissionAllDecorator('hg.admin')
194 @HasPermissionAllDecorator('hg.admin')
201 @CSRFRequired()
195 @CSRFRequired()
202 def settings_vcs_delete_svn_pattern(self):
196 def settings_vcs_delete_svn_pattern(self):
203 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
197 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
204 model = VcsSettingsModel()
198 model = VcsSettingsModel()
205 try:
199 try:
206 model.delete_global_svn_pattern(delete_pattern_id)
200 model.delete_global_svn_pattern(delete_pattern_id)
207 except SettingNotFound:
201 except SettingNotFound:
208 log.exception(
202 log.exception(
209 'Failed to delete svn_pattern with id %s', delete_pattern_id)
203 'Failed to delete svn_pattern with id %s', delete_pattern_id)
210 raise HTTPNotFound()
204 raise HTTPNotFound()
211
205
212 Session().commit()
206 Session().commit()
213 return True
207 return True
214
208
215 @LoginRequired()
209 @LoginRequired()
216 @HasPermissionAllDecorator('hg.admin')
210 @HasPermissionAllDecorator('hg.admin')
217 def settings_mapping(self):
211 def settings_mapping(self):
218 c = self.load_default_context()
212 c = self.load_default_context()
219 c.active = 'mapping'
213 c.active = 'mapping'
220 c.storage_path = VcsSettingsModel().get_repos_location()
214 c.storage_path = get_rhodecode_repo_store_path()
221 data = render('rhodecode:templates/admin/settings/settings.mako',
215 data = render('rhodecode:templates/admin/settings/settings.mako',
222 self._get_template_context(c), self.request)
216 self._get_template_context(c), self.request)
223 html = formencode.htmlfill.render(
217 html = formencode.htmlfill.render(
224 data,
218 data,
225 defaults=self._form_defaults(),
219 defaults=self._form_defaults(),
226 encoding="UTF-8",
220 encoding="UTF-8",
227 force_defaults=False
221 force_defaults=False
228 )
222 )
229 return Response(html)
223 return Response(html)
230
224
231 @LoginRequired()
225 @LoginRequired()
232 @HasPermissionAllDecorator('hg.admin')
226 @HasPermissionAllDecorator('hg.admin')
233 @CSRFRequired()
227 @CSRFRequired()
234 def settings_mapping_update(self):
228 def settings_mapping_update(self):
235 _ = self.request.translate
229 _ = self.request.translate
236 c = self.load_default_context()
230 c = self.load_default_context()
237 c.active = 'mapping'
231 c.active = 'mapping'
238 rm_obsolete = self.request.POST.get('destroy', False)
232 rm_obsolete = self.request.POST.get('destroy', False)
239 invalidate_cache = self.request.POST.get('invalidate', False)
233 invalidate_cache = self.request.POST.get('invalidate', False)
240 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
234 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
241
235
242 if invalidate_cache:
236 if invalidate_cache:
243 log.debug('invalidating all repositories cache')
237 log.debug('invalidating all repositories cache')
244 for repo in Repository.get_all():
238 for repo in Repository.get_all():
245 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
239 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
246
240
247 filesystem_repos = ScmModel().repo_scan()
241 filesystem_repos = ScmModel().repo_scan()
248 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete, force_hooks_rebuild=True)
242 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete, force_hooks_rebuild=True)
249 PermissionModel().trigger_permission_flush()
243 PermissionModel().trigger_permission_flush()
250
244
251 def _repr(rm_repo):
245 def _repr(rm_repo):
252 return ', '.join(map(safe_str, rm_repo)) or '-'
246 return ', '.join(map(safe_str, rm_repo)) or '-'
253
247
254 h.flash(_('Repositories successfully '
248 h.flash(_('Repositories successfully '
255 'rescanned added: %s ; removed: %s') %
249 'rescanned added: %s ; removed: %s') %
256 (_repr(added), _repr(removed)),
250 (_repr(added), _repr(removed)),
257 category='success')
251 category='success')
258 raise HTTPFound(h.route_path('admin_settings_mapping'))
252 raise HTTPFound(h.route_path('admin_settings_mapping'))
259
253
260 @LoginRequired()
254 @LoginRequired()
261 @HasPermissionAllDecorator('hg.admin')
255 @HasPermissionAllDecorator('hg.admin')
262 def settings_global(self):
256 def settings_global(self):
263 c = self.load_default_context()
257 c = self.load_default_context()
264 c.active = 'global'
258 c.active = 'global'
265 c.personal_repo_group_default_pattern = RepoGroupModel()\
259 c.personal_repo_group_default_pattern = RepoGroupModel()\
266 .get_personal_group_name_pattern()
260 .get_personal_group_name_pattern()
267
261
268 data = render('rhodecode:templates/admin/settings/settings.mako',
262 data = render('rhodecode:templates/admin/settings/settings.mako',
269 self._get_template_context(c), self.request)
263 self._get_template_context(c), self.request)
270 html = formencode.htmlfill.render(
264 html = formencode.htmlfill.render(
271 data,
265 data,
272 defaults=self._form_defaults(),
266 defaults=self._form_defaults(),
273 encoding="UTF-8",
267 encoding="UTF-8",
274 force_defaults=False
268 force_defaults=False
275 )
269 )
276 return Response(html)
270 return Response(html)
277
271
278 @LoginRequired()
272 @LoginRequired()
279 @HasPermissionAllDecorator('hg.admin')
273 @HasPermissionAllDecorator('hg.admin')
280 @CSRFRequired()
274 @CSRFRequired()
281 def settings_global_update(self):
275 def settings_global_update(self):
282 _ = self.request.translate
276 _ = self.request.translate
283 c = self.load_default_context()
277 c = self.load_default_context()
284 c.active = 'global'
278 c.active = 'global'
285 c.personal_repo_group_default_pattern = RepoGroupModel()\
279 c.personal_repo_group_default_pattern = RepoGroupModel()\
286 .get_personal_group_name_pattern()
280 .get_personal_group_name_pattern()
287 application_form = ApplicationSettingsForm(self.request.translate)()
281 application_form = ApplicationSettingsForm(self.request.translate)()
288 try:
282 try:
289 form_result = application_form.to_python(dict(self.request.POST))
283 form_result = application_form.to_python(dict(self.request.POST))
290 except formencode.Invalid as errors:
284 except formencode.Invalid as errors:
291 h.flash(
285 h.flash(
292 _("Some form inputs contain invalid data."),
286 _("Some form inputs contain invalid data."),
293 category='error')
287 category='error')
294 data = render('rhodecode:templates/admin/settings/settings.mako',
288 data = render('rhodecode:templates/admin/settings/settings.mako',
295 self._get_template_context(c), self.request)
289 self._get_template_context(c), self.request)
296 html = formencode.htmlfill.render(
290 html = formencode.htmlfill.render(
297 data,
291 data,
298 defaults=errors.value,
292 defaults=errors.value,
299 errors=errors.unpack_errors() or {},
293 errors=errors.unpack_errors() or {},
300 prefix_error=False,
294 prefix_error=False,
301 encoding="UTF-8",
295 encoding="UTF-8",
302 force_defaults=False
296 force_defaults=False
303 )
297 )
304 return Response(html)
298 return Response(html)
305
299
306 settings = [
300 settings = [
307 ('title', 'rhodecode_title', 'unicode'),
301 ('title', 'rhodecode_title', 'unicode'),
308 ('realm', 'rhodecode_realm', 'unicode'),
302 ('realm', 'rhodecode_realm', 'unicode'),
309 ('pre_code', 'rhodecode_pre_code', 'unicode'),
303 ('pre_code', 'rhodecode_pre_code', 'unicode'),
310 ('post_code', 'rhodecode_post_code', 'unicode'),
304 ('post_code', 'rhodecode_post_code', 'unicode'),
311 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
305 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
312 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
306 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
313 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
307 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
314 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
308 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
315 ]
309 ]
316
310
317 try:
311 try:
318 for setting, form_key, type_ in settings:
312 for setting, form_key, type_ in settings:
319 sett = SettingsModel().create_or_update_setting(
313 sett = SettingsModel().create_or_update_setting(
320 setting, form_result[form_key], type_)
314 setting, form_result[form_key], type_)
321 Session().add(sett)
315 Session().add(sett)
322
316
323 Session().commit()
317 Session().commit()
324 SettingsModel().invalidate_settings_cache()
318 SettingsModel().invalidate_settings_cache()
325 h.flash(_('Updated application settings'), category='success')
319 h.flash(_('Updated application settings'), category='success')
326 except Exception:
320 except Exception:
327 log.exception("Exception while updating application settings")
321 log.exception("Exception while updating application settings")
328 h.flash(
322 h.flash(
329 _('Error occurred during updating application settings'),
323 _('Error occurred during updating application settings'),
330 category='error')
324 category='error')
331
325
332 raise HTTPFound(h.route_path('admin_settings_global'))
326 raise HTTPFound(h.route_path('admin_settings_global'))
333
327
334 @LoginRequired()
328 @LoginRequired()
335 @HasPermissionAllDecorator('hg.admin')
329 @HasPermissionAllDecorator('hg.admin')
336 def settings_visual(self):
330 def settings_visual(self):
337 c = self.load_default_context()
331 c = self.load_default_context()
338 c.active = 'visual'
332 c.active = 'visual'
339
333
340 data = render('rhodecode:templates/admin/settings/settings.mako',
334 data = render('rhodecode:templates/admin/settings/settings.mako',
341 self._get_template_context(c), self.request)
335 self._get_template_context(c), self.request)
342 html = formencode.htmlfill.render(
336 html = formencode.htmlfill.render(
343 data,
337 data,
344 defaults=self._form_defaults(),
338 defaults=self._form_defaults(),
345 encoding="UTF-8",
339 encoding="UTF-8",
346 force_defaults=False
340 force_defaults=False
347 )
341 )
348 return Response(html)
342 return Response(html)
349
343
350 @LoginRequired()
344 @LoginRequired()
351 @HasPermissionAllDecorator('hg.admin')
345 @HasPermissionAllDecorator('hg.admin')
352 @CSRFRequired()
346 @CSRFRequired()
353 def settings_visual_update(self):
347 def settings_visual_update(self):
354 _ = self.request.translate
348 _ = self.request.translate
355 c = self.load_default_context()
349 c = self.load_default_context()
356 c.active = 'visual'
350 c.active = 'visual'
357 application_form = ApplicationVisualisationForm(self.request.translate)()
351 application_form = ApplicationVisualisationForm(self.request.translate)()
358 try:
352 try:
359 form_result = application_form.to_python(dict(self.request.POST))
353 form_result = application_form.to_python(dict(self.request.POST))
360 except formencode.Invalid as errors:
354 except formencode.Invalid as errors:
361 h.flash(
355 h.flash(
362 _("Some form inputs contain invalid data."),
356 _("Some form inputs contain invalid data."),
363 category='error')
357 category='error')
364 data = render('rhodecode:templates/admin/settings/settings.mako',
358 data = render('rhodecode:templates/admin/settings/settings.mako',
365 self._get_template_context(c), self.request)
359 self._get_template_context(c), self.request)
366 html = formencode.htmlfill.render(
360 html = formencode.htmlfill.render(
367 data,
361 data,
368 defaults=errors.value,
362 defaults=errors.value,
369 errors=errors.unpack_errors() or {},
363 errors=errors.unpack_errors() or {},
370 prefix_error=False,
364 prefix_error=False,
371 encoding="UTF-8",
365 encoding="UTF-8",
372 force_defaults=False
366 force_defaults=False
373 )
367 )
374 return Response(html)
368 return Response(html)
375
369
376 try:
370 try:
377 settings = [
371 settings = [
378 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
372 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
379 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
373 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
380 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
374 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
381 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
375 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
382 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
376 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
383 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
377 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
384 ('show_version', 'rhodecode_show_version', 'bool'),
378 ('show_version', 'rhodecode_show_version', 'bool'),
385 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
379 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
386 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
380 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
387 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
381 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
388 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
382 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
389 ('clone_uri_id_tmpl', 'rhodecode_clone_uri_id_tmpl', 'unicode'),
383 ('clone_uri_id_tmpl', 'rhodecode_clone_uri_id_tmpl', 'unicode'),
390 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
384 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
391 ('support_url', 'rhodecode_support_url', 'unicode'),
385 ('support_url', 'rhodecode_support_url', 'unicode'),
392 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
386 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
393 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
387 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
394 ]
388 ]
395 for setting, form_key, type_ in settings:
389 for setting, form_key, type_ in settings:
396 sett = SettingsModel().create_or_update_setting(
390 sett = SettingsModel().create_or_update_setting(
397 setting, form_result[form_key], type_)
391 setting, form_result[form_key], type_)
398 Session().add(sett)
392 Session().add(sett)
399
393
400 Session().commit()
394 Session().commit()
401 SettingsModel().invalidate_settings_cache()
395 SettingsModel().invalidate_settings_cache()
402 h.flash(_('Updated visualisation settings'), category='success')
396 h.flash(_('Updated visualisation settings'), category='success')
403 except Exception:
397 except Exception:
404 log.exception("Exception updating visualization settings")
398 log.exception("Exception updating visualization settings")
405 h.flash(_('Error occurred during updating '
399 h.flash(_('Error occurred during updating '
406 'visualisation settings'),
400 'visualisation settings'),
407 category='error')
401 category='error')
408
402
409 raise HTTPFound(h.route_path('admin_settings_visual'))
403 raise HTTPFound(h.route_path('admin_settings_visual'))
410
404
411 @LoginRequired()
405 @LoginRequired()
412 @HasPermissionAllDecorator('hg.admin')
406 @HasPermissionAllDecorator('hg.admin')
413 def settings_issuetracker(self):
407 def settings_issuetracker(self):
414 c = self.load_default_context()
408 c = self.load_default_context()
415 c.active = 'issuetracker'
409 c.active = 'issuetracker'
416 defaults = c.rc_config
410 defaults = c.rc_config
417
411
418 entry_key = 'rhodecode_issuetracker_pat_'
412 entry_key = 'rhodecode_issuetracker_pat_'
419
413
420 c.issuetracker_entries = {}
414 c.issuetracker_entries = {}
421 for k, v in defaults.items():
415 for k, v in defaults.items():
422 if k.startswith(entry_key):
416 if k.startswith(entry_key):
423 uid = k[len(entry_key):]
417 uid = k[len(entry_key):]
424 c.issuetracker_entries[uid] = None
418 c.issuetracker_entries[uid] = None
425
419
426 for uid in c.issuetracker_entries:
420 for uid in c.issuetracker_entries:
427 c.issuetracker_entries[uid] = AttributeDict({
421 c.issuetracker_entries[uid] = AttributeDict({
428 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
422 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
429 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
423 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
430 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
424 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
431 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
425 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
432 })
426 })
433
427
434 return self._get_template_context(c)
428 return self._get_template_context(c)
435
429
436 @LoginRequired()
430 @LoginRequired()
437 @HasPermissionAllDecorator('hg.admin')
431 @HasPermissionAllDecorator('hg.admin')
438 @CSRFRequired()
432 @CSRFRequired()
439 def settings_issuetracker_test(self):
433 def settings_issuetracker_test(self):
440 error_container = []
434 error_container = []
441
435
442 urlified_commit = h.urlify_commit_message(
436 urlified_commit = h.urlify_commit_message(
443 self.request.POST.get('test_text', ''),
437 self.request.POST.get('test_text', ''),
444 'repo_group/test_repo1', error_container=error_container)
438 'repo_group/test_repo1', error_container=error_container)
445 if error_container:
439 if error_container:
446 def converter(inp):
440 def converter(inp):
447 return h.html_escape(inp)
441 return h.html_escape(inp)
448
442
449 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
443 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
450
444
451 return urlified_commit
445 return urlified_commit
452
446
453 @LoginRequired()
447 @LoginRequired()
454 @HasPermissionAllDecorator('hg.admin')
448 @HasPermissionAllDecorator('hg.admin')
455 @CSRFRequired()
449 @CSRFRequired()
456 def settings_issuetracker_update(self):
450 def settings_issuetracker_update(self):
457 _ = self.request.translate
451 _ = self.request.translate
458 self.load_default_context()
452 self.load_default_context()
459 settings_model = IssueTrackerSettingsModel()
453 settings_model = IssueTrackerSettingsModel()
460
454
461 try:
455 try:
462 form = IssueTrackerPatternsForm(self.request.translate)()
456 form = IssueTrackerPatternsForm(self.request.translate)()
463 data = form.to_python(self.request.POST)
457 data = form.to_python(self.request.POST)
464 except formencode.Invalid as errors:
458 except formencode.Invalid as errors:
465 log.exception('Failed to add new pattern')
459 log.exception('Failed to add new pattern')
466 error = errors
460 error = errors
467 h.flash(_(f'Invalid issue tracker pattern: {error}'),
461 h.flash(_(f'Invalid issue tracker pattern: {error}'),
468 category='error')
462 category='error')
469 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
463 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
470
464
471 if data:
465 if data:
472 for uid in data.get('delete_patterns', []):
466 for uid in data.get('delete_patterns', []):
473 settings_model.delete_entries(uid)
467 settings_model.delete_entries(uid)
474
468
475 for pattern in data.get('patterns', []):
469 for pattern in data.get('patterns', []):
476 for setting, value, type_ in pattern:
470 for setting, value, type_ in pattern:
477 sett = settings_model.create_or_update_setting(
471 sett = settings_model.create_or_update_setting(
478 setting, value, type_)
472 setting, value, type_)
479 Session().add(sett)
473 Session().add(sett)
480
474
481 Session().commit()
475 Session().commit()
482
476
483 SettingsModel().invalidate_settings_cache()
477 SettingsModel().invalidate_settings_cache()
484 h.flash(_('Updated issue tracker entries'), category='success')
478 h.flash(_('Updated issue tracker entries'), category='success')
485 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
479 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
486
480
487 @LoginRequired()
481 @LoginRequired()
488 @HasPermissionAllDecorator('hg.admin')
482 @HasPermissionAllDecorator('hg.admin')
489 @CSRFRequired()
483 @CSRFRequired()
490 def settings_issuetracker_delete(self):
484 def settings_issuetracker_delete(self):
491 _ = self.request.translate
485 _ = self.request.translate
492 self.load_default_context()
486 self.load_default_context()
493 uid = self.request.POST.get('uid')
487 uid = self.request.POST.get('uid')
494 try:
488 try:
495 IssueTrackerSettingsModel().delete_entries(uid)
489 IssueTrackerSettingsModel().delete_entries(uid)
496 except Exception:
490 except Exception:
497 log.exception('Failed to delete issue tracker setting %s', uid)
491 log.exception('Failed to delete issue tracker setting %s', uid)
498 raise HTTPNotFound()
492 raise HTTPNotFound()
499
493
500 SettingsModel().invalidate_settings_cache()
494 SettingsModel().invalidate_settings_cache()
501 h.flash(_('Removed issue tracker entry.'), category='success')
495 h.flash(_('Removed issue tracker entry.'), category='success')
502
496
503 return {'deleted': uid}
497 return {'deleted': uid}
504
498
505 @LoginRequired()
499 @LoginRequired()
506 @HasPermissionAllDecorator('hg.admin')
500 @HasPermissionAllDecorator('hg.admin')
507 def settings_email(self):
501 def settings_email(self):
508 c = self.load_default_context()
502 c = self.load_default_context()
509 c.active = 'email'
503 c.active = 'email'
510 c.rhodecode_ini = rhodecode.CONFIG
504 c.rhodecode_ini = rhodecode.CONFIG
511
505
512 data = render('rhodecode:templates/admin/settings/settings.mako',
506 data = render('rhodecode:templates/admin/settings/settings.mako',
513 self._get_template_context(c), self.request)
507 self._get_template_context(c), self.request)
514 html = formencode.htmlfill.render(
508 html = formencode.htmlfill.render(
515 data,
509 data,
516 defaults=self._form_defaults(),
510 defaults=self._form_defaults(),
517 encoding="UTF-8",
511 encoding="UTF-8",
518 force_defaults=False
512 force_defaults=False
519 )
513 )
520 return Response(html)
514 return Response(html)
521
515
522 @LoginRequired()
516 @LoginRequired()
523 @HasPermissionAllDecorator('hg.admin')
517 @HasPermissionAllDecorator('hg.admin')
524 @CSRFRequired()
518 @CSRFRequired()
525 def settings_email_update(self):
519 def settings_email_update(self):
526 _ = self.request.translate
520 _ = self.request.translate
527 c = self.load_default_context()
521 c = self.load_default_context()
528 c.active = 'email'
522 c.active = 'email'
529
523
530 test_email = self.request.POST.get('test_email')
524 test_email = self.request.POST.get('test_email')
531
525
532 if not test_email:
526 if not test_email:
533 h.flash(_('Please enter email address'), category='error')
527 h.flash(_('Please enter email address'), category='error')
534 raise HTTPFound(h.route_path('admin_settings_email'))
528 raise HTTPFound(h.route_path('admin_settings_email'))
535
529
536 email_kwargs = {
530 email_kwargs = {
537 'date': datetime.datetime.now(),
531 'date': datetime.datetime.now(),
538 'user': self._rhodecode_db_user
532 'user': self._rhodecode_db_user
539 }
533 }
540
534
541 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
535 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
542 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
536 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
543
537
544 recipients = [test_email] if test_email else None
538 recipients = [test_email] if test_email else None
545
539
546 run_task(tasks.send_email, recipients, subject,
540 run_task(tasks.send_email, recipients, subject,
547 email_body_plaintext, email_body)
541 email_body_plaintext, email_body)
548
542
549 h.flash(_('Send email task created'), category='success')
543 h.flash(_('Send email task created'), category='success')
550 raise HTTPFound(h.route_path('admin_settings_email'))
544 raise HTTPFound(h.route_path('admin_settings_email'))
551
545
552 @LoginRequired()
546 @LoginRequired()
553 @HasPermissionAllDecorator('hg.admin')
547 @HasPermissionAllDecorator('hg.admin')
554 def settings_hooks(self):
548 def settings_hooks(self):
555 c = self.load_default_context()
549 c = self.load_default_context()
556 c.active = 'hooks'
550 c.active = 'hooks'
557
551
558 model = SettingsModel()
552 model = SettingsModel()
559 c.hooks = model.get_builtin_hooks()
553 c.hooks = model.get_builtin_hooks()
560 c.custom_hooks = model.get_custom_hooks()
554 c.custom_hooks = model.get_custom_hooks()
561
555
562 data = render('rhodecode:templates/admin/settings/settings.mako',
556 data = render('rhodecode:templates/admin/settings/settings.mako',
563 self._get_template_context(c), self.request)
557 self._get_template_context(c), self.request)
564 html = formencode.htmlfill.render(
558 html = formencode.htmlfill.render(
565 data,
559 data,
566 defaults=self._form_defaults(),
560 defaults=self._form_defaults(),
567 encoding="UTF-8",
561 encoding="UTF-8",
568 force_defaults=False
562 force_defaults=False
569 )
563 )
570 return Response(html)
564 return Response(html)
571
565
572 @LoginRequired()
566 @LoginRequired()
573 @HasPermissionAllDecorator('hg.admin')
567 @HasPermissionAllDecorator('hg.admin')
574 @CSRFRequired()
568 @CSRFRequired()
575 def settings_hooks_update(self):
569 def settings_hooks_update(self):
576 _ = self.request.translate
570 _ = self.request.translate
577 c = self.load_default_context()
571 c = self.load_default_context()
578 c.active = 'hooks'
572 c.active = 'hooks'
579 if c.visual.allow_custom_hooks_settings:
573 if c.visual.allow_custom_hooks_settings:
580 ui_key = self.request.POST.get('new_hook_ui_key')
574 ui_key = self.request.POST.get('new_hook_ui_key')
581 ui_value = self.request.POST.get('new_hook_ui_value')
575 ui_value = self.request.POST.get('new_hook_ui_value')
582
576
583 hook_id = self.request.POST.get('hook_id')
577 hook_id = self.request.POST.get('hook_id')
584 new_hook = False
578 new_hook = False
585
579
586 model = SettingsModel()
580 model = SettingsModel()
587 try:
581 try:
588 if ui_value and ui_key:
582 if ui_value and ui_key:
589 model.create_or_update_hook(ui_key, ui_value)
583 model.create_or_update_hook(ui_key, ui_value)
590 h.flash(_('Added new hook'), category='success')
584 h.flash(_('Added new hook'), category='success')
591 new_hook = True
585 new_hook = True
592 elif hook_id:
586 elif hook_id:
593 RhodeCodeUi.delete(hook_id)
587 RhodeCodeUi.delete(hook_id)
594 Session().commit()
588 Session().commit()
595
589
596 # check for edits
590 # check for edits
597 update = False
591 update = False
598 _d = self.request.POST.dict_of_lists()
592 _d = self.request.POST.dict_of_lists()
599 for k, v in zip(_d.get('hook_ui_key', []),
593 for k, v in zip(_d.get('hook_ui_key', []),
600 _d.get('hook_ui_value_new', [])):
594 _d.get('hook_ui_value_new', [])):
601 model.create_or_update_hook(k, v)
595 model.create_or_update_hook(k, v)
602 update = True
596 update = True
603
597
604 if update and not new_hook:
598 if update and not new_hook:
605 h.flash(_('Updated hooks'), category='success')
599 h.flash(_('Updated hooks'), category='success')
606 Session().commit()
600 Session().commit()
607 except Exception:
601 except Exception:
608 log.exception("Exception during hook creation")
602 log.exception("Exception during hook creation")
609 h.flash(_('Error occurred during hook creation'),
603 h.flash(_('Error occurred during hook creation'),
610 category='error')
604 category='error')
611
605
612 raise HTTPFound(h.route_path('admin_settings_hooks'))
606 raise HTTPFound(h.route_path('admin_settings_hooks'))
613
607
614 @LoginRequired()
608 @LoginRequired()
615 @HasPermissionAllDecorator('hg.admin')
609 @HasPermissionAllDecorator('hg.admin')
616 def settings_search(self):
610 def settings_search(self):
617 c = self.load_default_context()
611 c = self.load_default_context()
618 c.active = 'search'
612 c.active = 'search'
619
613
620 c.searcher = searcher_from_config(self.request.registry.settings)
614 c.searcher = searcher_from_config(self.request.registry.settings)
621 c.statistics = c.searcher.statistics(self.request.translate)
615 c.statistics = c.searcher.statistics(self.request.translate)
622
616
623 return self._get_template_context(c)
617 return self._get_template_context(c)
624
618
625 @LoginRequired()
619 @LoginRequired()
626 @HasPermissionAllDecorator('hg.admin')
620 @HasPermissionAllDecorator('hg.admin')
627 def settings_labs(self):
621 def settings_labs(self):
628 c = self.load_default_context()
622 c = self.load_default_context()
629 if not c.labs_active:
623 if not c.labs_active:
630 raise HTTPFound(h.route_path('admin_settings'))
624 raise HTTPFound(h.route_path('admin_settings'))
631
625
632 c.active = 'labs'
626 c.active = 'labs'
633 c.lab_settings = _LAB_SETTINGS
627 c.lab_settings = _LAB_SETTINGS
634
628
635 data = render('rhodecode:templates/admin/settings/settings.mako',
629 data = render('rhodecode:templates/admin/settings/settings.mako',
636 self._get_template_context(c), self.request)
630 self._get_template_context(c), self.request)
637 html = formencode.htmlfill.render(
631 html = formencode.htmlfill.render(
638 data,
632 data,
639 defaults=self._form_defaults(),
633 defaults=self._form_defaults(),
640 encoding="UTF-8",
634 encoding="UTF-8",
641 force_defaults=False
635 force_defaults=False
642 )
636 )
643 return Response(html)
637 return Response(html)
644
638
645 @LoginRequired()
639 @LoginRequired()
646 @HasPermissionAllDecorator('hg.admin')
640 @HasPermissionAllDecorator('hg.admin')
647 @CSRFRequired()
641 @CSRFRequired()
648 def settings_labs_update(self):
642 def settings_labs_update(self):
649 _ = self.request.translate
643 _ = self.request.translate
650 c = self.load_default_context()
644 c = self.load_default_context()
651 c.active = 'labs'
645 c.active = 'labs'
652
646
653 application_form = LabsSettingsForm(self.request.translate)()
647 application_form = LabsSettingsForm(self.request.translate)()
654 try:
648 try:
655 form_result = application_form.to_python(dict(self.request.POST))
649 form_result = application_form.to_python(dict(self.request.POST))
656 except formencode.Invalid as errors:
650 except formencode.Invalid as errors:
657 h.flash(
651 h.flash(
658 _("Some form inputs contain invalid data."),
652 _("Some form inputs contain invalid data."),
659 category='error')
653 category='error')
660 data = render('rhodecode:templates/admin/settings/settings.mako',
654 data = render('rhodecode:templates/admin/settings/settings.mako',
661 self._get_template_context(c), self.request)
655 self._get_template_context(c), self.request)
662 html = formencode.htmlfill.render(
656 html = formencode.htmlfill.render(
663 data,
657 data,
664 defaults=errors.value,
658 defaults=errors.value,
665 errors=errors.unpack_errors() or {},
659 errors=errors.unpack_errors() or {},
666 prefix_error=False,
660 prefix_error=False,
667 encoding="UTF-8",
661 encoding="UTF-8",
668 force_defaults=False
662 force_defaults=False
669 )
663 )
670 return Response(html)
664 return Response(html)
671
665
672 try:
666 try:
673 session = Session()
667 session = Session()
674 for setting in _LAB_SETTINGS:
668 for setting in _LAB_SETTINGS:
675 setting_name = setting.key[len('rhodecode_'):]
669 setting_name = setting.key[len('rhodecode_'):]
676 sett = SettingsModel().create_or_update_setting(
670 sett = SettingsModel().create_or_update_setting(
677 setting_name, form_result[setting.key], setting.type)
671 setting_name, form_result[setting.key], setting.type)
678 session.add(sett)
672 session.add(sett)
679
673
680 except Exception:
674 except Exception:
681 log.exception('Exception while updating lab settings')
675 log.exception('Exception while updating lab settings')
682 h.flash(_('Error occurred during updating labs settings'),
676 h.flash(_('Error occurred during updating labs settings'),
683 category='error')
677 category='error')
684 else:
678 else:
685 Session().commit()
679 Session().commit()
686 SettingsModel().invalidate_settings_cache()
680 SettingsModel().invalidate_settings_cache()
687 h.flash(_('Updated Labs settings'), category='success')
681 h.flash(_('Updated Labs settings'), category='success')
688 raise HTTPFound(h.route_path('admin_settings_labs'))
682 raise HTTPFound(h.route_path('admin_settings_labs'))
689
683
690 data = render('rhodecode:templates/admin/settings/settings.mako',
684 data = render('rhodecode:templates/admin/settings/settings.mako',
691 self._get_template_context(c), self.request)
685 self._get_template_context(c), self.request)
692 html = formencode.htmlfill.render(
686 html = formencode.htmlfill.render(
693 data,
687 data,
694 defaults=self._form_defaults(),
688 defaults=self._form_defaults(),
695 encoding="UTF-8",
689 encoding="UTF-8",
696 force_defaults=False
690 force_defaults=False
697 )
691 )
698 return Response(html)
692 return Response(html)
699
693
700
694
701 # :param key: name of the setting including the 'rhodecode_' prefix
695 # :param key: name of the setting including the 'rhodecode_' prefix
702 # :param type: the RhodeCodeSetting type to use.
696 # :param type: the RhodeCodeSetting type to use.
703 # :param group: the i18ned group in which we should dispaly this setting
697 # :param group: the i18ned group in which we should dispaly this setting
704 # :param label: the i18ned label we should display for this setting
698 # :param label: the i18ned label we should display for this setting
705 # :param help: the i18ned help we should dispaly for this setting
699 # :param help: the i18ned help we should dispaly for this setting
706 LabSetting = collections.namedtuple(
700 LabSetting = collections.namedtuple(
707 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
701 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
708
702
709
703
710 # This list has to be kept in sync with the form
704 # This list has to be kept in sync with the form
711 # rhodecode.model.forms.LabsSettingsForm.
705 # rhodecode.model.forms.LabsSettingsForm.
712 _LAB_SETTINGS = [
706 _LAB_SETTINGS = [
713
707
714 ]
708 ]
@@ -1,237 +1,243 b''
1
1
2
2
3 # Copyright (C) 2016-2023 RhodeCode GmbH
3 # Copyright (C) 2016-2023 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import urllib.request
22 import urllib.request
23 import urllib.error
23 import urllib.error
24 import urllib.parse
24 import urllib.parse
25 import os
25 import os
26
26
27 import rhodecode
27 import rhodecode
28 from rhodecode.apps._base import BaseAppView
28 from rhodecode.apps._base import BaseAppView
29 from rhodecode.apps._base.navigation import navigation_list
29 from rhodecode.apps._base.navigation import navigation_list
30 from rhodecode.lib import helpers as h
30 from rhodecode.lib import helpers as h
31 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
31 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
32 from rhodecode.lib.utils2 import str2bool
32 from rhodecode.lib.utils2 import str2bool
33 from rhodecode.lib import system_info
33 from rhodecode.lib import system_info
34 from rhodecode.model.update import UpdateModel
34 from rhodecode.model.update import UpdateModel
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 class AdminSystemInfoSettingsView(BaseAppView):
39 class AdminSystemInfoSettingsView(BaseAppView):
40 def load_default_context(self):
40 def load_default_context(self):
41 c = self._get_local_tmpl_context()
41 c = self._get_local_tmpl_context()
42 return c
42 return c
43
43
44 def get_env_data(self):
44 def get_env_data(self):
45 black_list = [
45 black_list = [
46 'NIX_LDFLAGS',
46 'NIX_LDFLAGS',
47 'NIX_CFLAGS_COMPILE',
47 'NIX_CFLAGS_COMPILE',
48 'propagatedBuildInputs',
48 'propagatedBuildInputs',
49 'propagatedNativeBuildInputs',
49 'propagatedNativeBuildInputs',
50 'postInstall',
50 'postInstall',
51 'buildInputs',
51 'buildInputs',
52 'buildPhase',
52 'buildPhase',
53 'preShellHook',
53 'preShellHook',
54 'preShellHook',
54 'preShellHook',
55 'preCheck',
55 'preCheck',
56 'preBuild',
56 'preBuild',
57 'postShellHook',
57 'postShellHook',
58 'postFixup',
58 'postFixup',
59 'postCheck',
59 'postCheck',
60 'nativeBuildInputs',
60 'nativeBuildInputs',
61 'installPhase',
61 'installPhase',
62 'installCheckPhase',
62 'installCheckPhase',
63 'checkPhase',
63 'checkPhase',
64 'configurePhase',
64 'configurePhase',
65 'shellHook'
65 'shellHook'
66 ]
66 ]
67 secret_list = [
67 secret_list = [
68 'RHODECODE_USER_PASS'
68 'RHODECODE_USER_PASS'
69 ]
69 ]
70
70
71 for k, v in sorted(os.environ.items()):
71 for k, v in sorted(os.environ.items()):
72 if k in black_list:
72 if k in black_list:
73 continue
73 continue
74 if k in secret_list:
74 if k in secret_list:
75 v = '*****'
75 v = '*****'
76 yield k, v
76 yield k, v
77
77
78 @LoginRequired()
78 @LoginRequired()
79 @HasPermissionAllDecorator('hg.admin')
79 @HasPermissionAllDecorator('hg.admin')
80 def settings_system_info(self):
80 def settings_system_info(self):
81 _ = self.request.translate
81 _ = self.request.translate
82 c = self.load_default_context()
82 c = self.load_default_context()
83
83
84 c.active = 'system'
84 c.active = 'system'
85 c.navlist = navigation_list(self.request)
85 c.navlist = navigation_list(self.request)
86
86
87 # TODO(marcink), figure out how to allow only selected users to do this
87 # TODO(marcink), figure out how to allow only selected users to do this
88 c.allowed_to_snapshot = self._rhodecode_user.admin
88 c.allowed_to_snapshot = self._rhodecode_user.admin
89
89
90 snapshot = str2bool(self.request.params.get('snapshot'))
90 snapshot = str2bool(self.request.params.get('snapshot'))
91
91
92 c.rhodecode_update_url = UpdateModel().get_update_url()
92 c.rhodecode_update_url = UpdateModel().get_update_url()
93 c.env_data = self.get_env_data()
93 c.env_data = self.get_env_data()
94 server_info = system_info.get_system_info(self.request.environ)
94 server_info = system_info.get_system_info(self.request.environ)
95
95
96 for key, val in server_info.items():
96 for key, val in server_info.items():
97 setattr(c, key, val)
97 setattr(c, key, val)
98
98
99 def val(name, subkey='human_value'):
99 def val(name, subkey='human_value'):
100 return server_info[name][subkey]
100 return server_info[name][subkey]
101
101
102 def state(name):
102 def state(name):
103 return server_info[name]['state']
103 return server_info[name]['state']
104
104
105 def val2(name):
105 def val2(name):
106 val = server_info[name]['human_value']
106 val = server_info[name]['human_value']
107 state = server_info[name]['state']
107 state = server_info[name]['state']
108 return val, state
108 return val, state
109
109
110 update_info_msg = _('Note: please make sure this server can '
110 update_info_msg = _('Note: please make sure this server can '
111 'access `${url}` for the update link to work',
111 'access `${url}` for the update link to work',
112 mapping=dict(url=c.rhodecode_update_url))
112 mapping=dict(url=c.rhodecode_update_url))
113 version = UpdateModel().get_stored_version()
113 version = UpdateModel().get_stored_version()
114 is_outdated = UpdateModel().is_outdated(
114 is_outdated = UpdateModel().is_outdated(
115 rhodecode.__version__, version)
115 rhodecode.__version__, version)
116 update_state = {
116 update_state = {
117 'type': 'warning',
117 'type': 'warning',
118 'message': 'New version available: {}'.format(version)
118 'message': 'New version available: {}'.format(version)
119 } \
119 } \
120 if is_outdated else {}
120 if is_outdated else {}
121 c.data_items = [
121 c.data_items = [
122 # update info
122 # update info
123 (_('Update info'), h.literal(
123 (_('Update info'), h.literal(
124 '<span class="link" id="check_for_update" >%s.</span>' % (
124 '<span class="link" id="check_for_update" >%s.</span>' % (
125 _('Check for updates')) +
125 _('Check for updates')) +
126 '<br/> <span >%s.</span>' % (update_info_msg)
126 '<br/> <span >%s.</span>' % (update_info_msg)
127 ), ''),
127 ), ''),
128
128
129 # RhodeCode specific
129 # RhodeCode specific
130 (_('RhodeCode Version'), val('rhodecode_app')['text'], state('rhodecode_app')),
130 (_('RhodeCode Version'), val('rhodecode_app')['text'], state('rhodecode_app')),
131 (_('Latest version'), version, update_state),
131 (_('Latest version'), version, update_state),
132 (_('RhodeCode Base URL'), val('rhodecode_config')['config'].get('app.base_url'), state('rhodecode_config')),
132 (_('RhodeCode Base URL'), val('rhodecode_config')['config'].get('app.base_url'), state('rhodecode_config')),
133 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
133 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
134 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
134 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
135 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
135 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
136 (_('RhodeCode Certificate'), val('rhodecode_config')['cert_path'], state('rhodecode_config')),
136 (_('RhodeCode Certificate'), val('rhodecode_config')['cert_path'], state('rhodecode_config')),
137 (_('Workers'), val('rhodecode_config')['config']['server:main'].get('workers', '?'), state('rhodecode_config')),
137 (_('Workers'), val('rhodecode_config')['config']['server:main'].get('workers', '?'), state('rhodecode_config')),
138 (_('Worker Type'), val('rhodecode_config')['config']['server:main'].get('worker_class', 'sync'), state('rhodecode_config')),
138 (_('Worker Type'), val('rhodecode_config')['config']['server:main'].get('worker_class', 'sync'), state('rhodecode_config')),
139 ('', '', ''), # spacer
139 ('', '', ''), # spacer
140
140
141 # Database
141 # Database
142 (_('Database'), val('database')['url'], state('database')),
142 (_('Database'), val('database')['url'], state('database')),
143 (_('Database version'), val('database')['version'], state('database')),
143 (_('Database version'), val('database')['version'], state('database')),
144 ('', '', ''), # spacer
144 ('', '', ''), # spacer
145
145
146 # Platform/Python
146 # Platform/Python
147 (_('Platform'), val('platform')['name'], state('platform')),
147 (_('Platform'), val('platform')['name'], state('platform')),
148 (_('Platform UUID'), val('platform')['uuid'], state('platform')),
148 (_('Platform UUID'), val('platform')['uuid'], state('platform')),
149 (_('Lang'), val('locale'), state('locale')),
149 (_('Lang'), val('locale'), state('locale')),
150 (_('Python version'), val('python')['version'], state('python')),
150 (_('Python version'), val('python')['version'], state('python')),
151 (_('Python path'), val('python')['executable'], state('python')),
151 (_('Python path'), val('python')['executable'], state('python')),
152 ('', '', ''), # spacer
152 ('', '', ''), # spacer
153
153
154 # Systems stats
154 # Systems stats
155 (_('CPU'), val('cpu')['text'], state('cpu')),
155 (_('CPU'), val('cpu')['text'], state('cpu')),
156 (_('Load'), val('load')['text'], state('load')),
156 (_('Load'), val('load')['text'], state('load')),
157 (_('Memory'), val('memory')['text'], state('memory')),
157 (_('Memory'), val('memory')['text'], state('memory')),
158 (_('Uptime'), val('uptime')['text'], state('uptime')),
158 (_('Uptime'), val('uptime')['text'], state('uptime')),
159 ('', '', ''), # spacer
159 ('', '', ''), # spacer
160
160
161 # ulimit
161 # ulimit
162 (_('Ulimit'), val('ulimit')['text'], state('ulimit')),
162 (_('Ulimit'), val('ulimit')['text'], state('ulimit')),
163
163
164 # Repo storage
164 # Repo storage
165 (_('Storage location'), val('storage')['path'], state('storage')),
165 (_('Storage location'), val('storage')['path'], state('storage')),
166 (_('Storage info'), val('storage')['text'], state('storage')),
166 (_('Storage info'), val('storage')['text'], state('storage')),
167 (_('Storage inodes'), val('storage_inodes')['text'], state('storage_inodes')),
167 (_('Storage inodes'), val('storage_inodes')['text'], state('storage_inodes')),
168 ('', '', ''), # spacer
168
169
169 (_('Gist storage location'), val('storage_gist')['path'], state('storage_gist')),
170 (_('Gist storage location'), val('storage_gist')['path'], state('storage_gist')),
170 (_('Gist storage info'), val('storage_gist')['text'], state('storage_gist')),
171 (_('Gist storage info'), val('storage_gist')['text'], state('storage_gist')),
172 ('', '', ''), # spacer
171
173
174 (_('Archive cache storage type'), val('storage_archive')['type'], state('storage_archive')),
172 (_('Archive cache storage location'), val('storage_archive')['path'], state('storage_archive')),
175 (_('Archive cache storage location'), val('storage_archive')['path'], state('storage_archive')),
173 (_('Archive cache info'), val('storage_archive')['text'], state('storage_archive')),
176 (_('Archive cache info'), val('storage_archive')['text'], state('storage_archive')),
177 ('', '', ''), # spacer
174
178
175 (_('Temp storage location'), val('storage_temp')['path'], state('storage_temp')),
179 (_('Temp storage location'), val('storage_temp')['path'], state('storage_temp')),
176 (_('Temp storage info'), val('storage_temp')['text'], state('storage_temp')),
180 (_('Temp storage info'), val('storage_temp')['text'], state('storage_temp')),
181 ('', '', ''), # spacer
177
182
178 (_('Search info'), val('search')['text'], state('search')),
183 (_('Search info'), val('search')['text'], state('search')),
179 (_('Search location'), val('search')['location'], state('search')),
184 (_('Search location'), val('search')['location'], state('search')),
180 ('', '', ''), # spacer
185 ('', '', ''), # spacer
181
186
182 # VCS specific
187 # VCS specific
183 (_('VCS Backends'), val('vcs_backends'), state('vcs_backends')),
188 (_('VCS Backends'), val('vcs_backends'), state('vcs_backends')),
184 (_('VCS Server'), val('vcs_server')['text'], state('vcs_server')),
189 (_('VCS Server'), val('vcs_server')['text'], state('vcs_server')),
185 (_('GIT'), val('git'), state('git')),
190 (_('GIT'), val('git'), state('git')),
186 (_('HG'), val('hg'), state('hg')),
191 (_('HG'), val('hg'), state('hg')),
187 (_('SVN'), val('svn'), state('svn')),
192 (_('SVN'), val('svn'), state('svn')),
188
193
189 ]
194 ]
190
195
191 c.vcsserver_data_items = [
196 c.vcsserver_data_items = [
192 (k, v) for k,v in (val('vcs_server_config') or {}).items()
197 (k, v) for k, v in (val('vcs_server_config') or {}).items()
193 ]
198 ]
194
199
195 if snapshot:
200 if snapshot:
196 if c.allowed_to_snapshot:
201 if c.allowed_to_snapshot:
197 c.data_items.pop(0) # remove server info
202 c.data_items.pop(0) # remove server info
198 self.request.override_renderer = 'admin/settings/settings_system_snapshot.mako'
203 self.request.override_renderer = 'admin/settings/settings_system_snapshot.mako'
199 else:
204 else:
200 h.flash('You are not allowed to do this', category='warning')
205 h.flash('You are not allowed to do this', category='warning')
201 return self._get_template_context(c)
206 return self._get_template_context(c)
202
207
203 @LoginRequired()
208 @LoginRequired()
204 @HasPermissionAllDecorator('hg.admin')
209 @HasPermissionAllDecorator('hg.admin')
205 def settings_system_info_check_update(self):
210 def settings_system_info_check_update(self):
206 _ = self.request.translate
211 _ = self.request.translate
207 c = self.load_default_context()
212 c = self.load_default_context()
208
213
209 update_url = UpdateModel().get_update_url()
214 update_url = UpdateModel().get_update_url()
210
215
211 def _err(s):
216 def _err(s):
212 return '<div style="color:#ff8888; padding:4px 0px">{}</div>'.format(s)
217 return f'<div style="color:#ff8888; padding:4px 0px">{s}</div>'
218
213 try:
219 try:
214 data = UpdateModel().get_update_data(update_url)
220 data = UpdateModel().get_update_data(update_url)
215 except urllib.error.URLError as e:
221 except urllib.error.URLError as e:
216 log.exception("Exception contacting upgrade server")
222 log.exception("Exception contacting upgrade server")
217 self.request.override_renderer = 'string'
223 self.request.override_renderer = 'string'
218 return _err('Failed to contact upgrade server: %r' % e)
224 return _err('Failed to contact upgrade server: %r' % e)
219 except ValueError as e:
225 except ValueError as e:
220 log.exception("Bad data sent from update server")
226 log.exception("Bad data sent from update server")
221 self.request.override_renderer = 'string'
227 self.request.override_renderer = 'string'
222 return _err('Bad data sent from update server')
228 return _err('Bad data sent from update server')
223
229
224 latest = data['versions'][0]
230 latest = data['versions'][0]
225
231
226 c.update_url = update_url
232 c.update_url = update_url
227 c.latest_data = latest
233 c.latest_data = latest
228 c.latest_ver = latest['version']
234 c.latest_ver = (latest['version'] or '').strip()
229 c.cur_ver = rhodecode.__version__
235 c.cur_ver = self.request.GET.get('ver') or rhodecode.__version__
230 c.should_upgrade = False
236 c.should_upgrade = False
231
237
232 is_oudated = UpdateModel().is_outdated(c.cur_ver, c.latest_ver)
238 is_outdated = UpdateModel().is_outdated(c.cur_ver, c.latest_ver)
233 if is_oudated:
239 if is_outdated:
234 c.should_upgrade = True
240 c.should_upgrade = True
235 c.important_notices = latest['general']
241 c.important_notices = latest['general']
236 UpdateModel().store_version(latest['version'])
242 UpdateModel().store_version(latest['version'])
237 return self._get_template_context(c)
243 return self._get_template_context(c)
@@ -1,1321 +1,1321 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import logging
19 import logging
20 import datetime
20 import datetime
21 import formencode
21 import formencode
22 import formencode.htmlfill
22 import formencode.htmlfill
23
23
24 from pyramid.httpexceptions import HTTPFound
24 from pyramid.httpexceptions import HTTPFound
25 from pyramid.renderers import render
25 from pyramid.renderers import render
26 from pyramid.response import Response
26 from pyramid.response import Response
27
27
28 from rhodecode import events
28 from rhodecode import events
29 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
29 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
30 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
30 from rhodecode.apps.ssh_support.events import SshKeyFileChangeEvent
31 from rhodecode.authentication.base import get_authn_registry, RhodeCodeExternalAuthPlugin
31 from rhodecode.authentication.base import get_authn_registry, RhodeCodeExternalAuthPlugin
32 from rhodecode.authentication.plugins import auth_rhodecode
32 from rhodecode.authentication.plugins import auth_rhodecode
33 from rhodecode.events import trigger
33 from rhodecode.events import trigger
34 from rhodecode.model.db import true, UserNotice
34 from rhodecode.model.db import true, UserNotice
35
35
36 from rhodecode.lib import audit_logger, rc_cache, auth
36 from rhodecode.lib import audit_logger, rc_cache, auth
37 from rhodecode.lib.exceptions import (
37 from rhodecode.lib.exceptions import (
38 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
38 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
39 UserOwnsUserGroupsException, UserOwnsPullRequestsException,
39 UserOwnsUserGroupsException, UserOwnsPullRequestsException,
40 UserOwnsArtifactsException, DefaultUserException)
40 UserOwnsArtifactsException, DefaultUserException)
41 from rhodecode.lib import ext_json
41 from rhodecode.lib import ext_json
42 from rhodecode.lib.auth import (
42 from rhodecode.lib.auth import (
43 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
43 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
44 from rhodecode.lib import helpers as h
44 from rhodecode.lib import helpers as h
45 from rhodecode.lib.helpers import SqlPage
45 from rhodecode.lib.helpers import SqlPage
46 from rhodecode.lib.utils2 import safe_int, safe_str, AttributeDict
46 from rhodecode.lib.utils2 import safe_int, safe_str, AttributeDict
47 from rhodecode.model.auth_token import AuthTokenModel
47 from rhodecode.model.auth_token import AuthTokenModel
48 from rhodecode.model.forms import (
48 from rhodecode.model.forms import (
49 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
49 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
50 UserExtraEmailForm, UserExtraIpForm)
50 UserExtraEmailForm, UserExtraIpForm)
51 from rhodecode.model.permission import PermissionModel
51 from rhodecode.model.permission import PermissionModel
52 from rhodecode.model.repo_group import RepoGroupModel
52 from rhodecode.model.repo_group import RepoGroupModel
53 from rhodecode.model.ssh_key import SshKeyModel
53 from rhodecode.model.ssh_key import SshKeyModel
54 from rhodecode.model.user import UserModel
54 from rhodecode.model.user import UserModel
55 from rhodecode.model.user_group import UserGroupModel
55 from rhodecode.model.user_group import UserGroupModel
56 from rhodecode.model.db import (
56 from rhodecode.model.db import (
57 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
57 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
58 UserApiKeys, UserSshKeys, RepoGroup)
58 UserApiKeys, UserSshKeys, RepoGroup)
59 from rhodecode.model.meta import Session
59 from rhodecode.model.meta import Session
60
60
61 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
62
62
63
63
64 class AdminUsersView(BaseAppView, DataGridAppView):
64 class AdminUsersView(BaseAppView, DataGridAppView):
65
65
66 def load_default_context(self):
66 def load_default_context(self):
67 c = self._get_local_tmpl_context()
67 c = self._get_local_tmpl_context()
68 return c
68 return c
69
69
70 @LoginRequired()
70 @LoginRequired()
71 @HasPermissionAllDecorator('hg.admin')
71 @HasPermissionAllDecorator('hg.admin')
72 def users_list(self):
72 def users_list(self):
73 c = self.load_default_context()
73 c = self.load_default_context()
74 return self._get_template_context(c)
74 return self._get_template_context(c)
75
75
76 @LoginRequired()
76 @LoginRequired()
77 @HasPermissionAllDecorator('hg.admin')
77 @HasPermissionAllDecorator('hg.admin')
78 def users_list_data(self):
78 def users_list_data(self):
79 self.load_default_context()
79 self.load_default_context()
80 column_map = {
80 column_map = {
81 'first_name': 'name',
81 'first_name': 'name',
82 'last_name': 'lastname',
82 'last_name': 'lastname',
83 }
83 }
84 draw, start, limit = self._extract_chunk(self.request)
84 draw, start, limit = self._extract_chunk(self.request)
85 search_q, order_by, order_dir = self._extract_ordering(
85 search_q, order_by, order_dir = self._extract_ordering(
86 self.request, column_map=column_map)
86 self.request, column_map=column_map)
87 _render = self.request.get_partial_renderer(
87 _render = self.request.get_partial_renderer(
88 'rhodecode:templates/data_table/_dt_elements.mako')
88 'rhodecode:templates/data_table/_dt_elements.mako')
89
89
90 def user_actions(user_id, username):
90 def user_actions(user_id, username):
91 return _render("user_actions", user_id, username)
91 return _render("user_actions", user_id, username)
92
92
93 users_data_total_count = User.query()\
93 users_data_total_count = User.query()\
94 .filter(User.username != User.DEFAULT_USER) \
94 .filter(User.username != User.DEFAULT_USER) \
95 .count()
95 .count()
96
96
97 users_data_total_inactive_count = User.query()\
97 users_data_total_inactive_count = User.query()\
98 .filter(User.username != User.DEFAULT_USER) \
98 .filter(User.username != User.DEFAULT_USER) \
99 .filter(User.active != true())\
99 .filter(User.active != true())\
100 .count()
100 .count()
101
101
102 # json generate
102 # json generate
103 base_q = User.query().filter(User.username != User.DEFAULT_USER)
103 base_q = User.query().filter(User.username != User.DEFAULT_USER)
104 base_inactive_q = base_q.filter(User.active != true())
104 base_inactive_q = base_q.filter(User.active != true())
105
105
106 if search_q:
106 if search_q:
107 like_expression = '%{}%'.format(safe_str(search_q))
107 like_expression = '%{}%'.format(safe_str(search_q))
108 base_q = base_q.filter(or_(
108 base_q = base_q.filter(or_(
109 User.username.ilike(like_expression),
109 User.username.ilike(like_expression),
110 User._email.ilike(like_expression),
110 User._email.ilike(like_expression),
111 User.name.ilike(like_expression),
111 User.name.ilike(like_expression),
112 User.lastname.ilike(like_expression),
112 User.lastname.ilike(like_expression),
113 ))
113 ))
114 base_inactive_q = base_q.filter(User.active != true())
114 base_inactive_q = base_q.filter(User.active != true())
115
115
116 users_data_total_filtered_count = base_q.count()
116 users_data_total_filtered_count = base_q.count()
117 users_data_total_filtered_inactive_count = base_inactive_q.count()
117 users_data_total_filtered_inactive_count = base_inactive_q.count()
118
118
119 sort_col = getattr(User, order_by, None)
119 sort_col = getattr(User, order_by, None)
120 if sort_col:
120 if sort_col:
121 if order_dir == 'asc':
121 if order_dir == 'asc':
122 # handle null values properly to order by NULL last
122 # handle null values properly to order by NULL last
123 if order_by in ['last_activity']:
123 if order_by in ['last_activity']:
124 sort_col = coalesce(sort_col, datetime.date.max)
124 sort_col = coalesce(sort_col, datetime.date.max)
125 sort_col = sort_col.asc()
125 sort_col = sort_col.asc()
126 else:
126 else:
127 # handle null values properly to order by NULL last
127 # handle null values properly to order by NULL last
128 if order_by in ['last_activity']:
128 if order_by in ['last_activity']:
129 sort_col = coalesce(sort_col, datetime.date.min)
129 sort_col = coalesce(sort_col, datetime.date.min)
130 sort_col = sort_col.desc()
130 sort_col = sort_col.desc()
131
131
132 base_q = base_q.order_by(sort_col)
132 base_q = base_q.order_by(sort_col)
133 base_q = base_q.offset(start).limit(limit)
133 base_q = base_q.offset(start).limit(limit)
134
134
135 users_list = base_q.all()
135 users_list = base_q.all()
136
136
137 users_data = []
137 users_data = []
138 for user in users_list:
138 for user in users_list:
139 users_data.append({
139 users_data.append({
140 "username": h.gravatar_with_user(self.request, user.username),
140 "username": h.gravatar_with_user(self.request, user.username),
141 "email": user.email,
141 "email": user.email,
142 "first_name": user.first_name,
142 "first_name": user.first_name,
143 "last_name": user.last_name,
143 "last_name": user.last_name,
144 "last_login": h.format_date(user.last_login),
144 "last_login": h.format_date(user.last_login),
145 "last_activity": h.format_date(user.last_activity),
145 "last_activity": h.format_date(user.last_activity),
146 "active": h.bool2icon(user.active),
146 "active": h.bool2icon(user.active),
147 "active_raw": user.active,
147 "active_raw": user.active,
148 "admin": h.bool2icon(user.admin),
148 "admin": h.bool2icon(user.admin),
149 "extern_type": user.extern_type,
149 "extern_type": user.extern_type,
150 "extern_name": user.extern_name,
150 "extern_name": user.extern_name,
151 "action": user_actions(user.user_id, user.username),
151 "action": user_actions(user.user_id, user.username),
152 })
152 })
153 data = ({
153 data = ({
154 'draw': draw,
154 'draw': draw,
155 'data': users_data,
155 'data': users_data,
156 'recordsTotal': users_data_total_count,
156 'recordsTotal': users_data_total_count,
157 'recordsFiltered': users_data_total_filtered_count,
157 'recordsFiltered': users_data_total_filtered_count,
158 'recordsTotalInactive': users_data_total_inactive_count,
158 'recordsTotalInactive': users_data_total_inactive_count,
159 'recordsFilteredInactive': users_data_total_filtered_inactive_count
159 'recordsFilteredInactive': users_data_total_filtered_inactive_count
160 })
160 })
161
161
162 return data
162 return data
163
163
164 def _set_personal_repo_group_template_vars(self, c_obj):
164 def _set_personal_repo_group_template_vars(self, c_obj):
165 DummyUser = AttributeDict({
165 DummyUser = AttributeDict({
166 'username': '${username}',
166 'username': '${username}',
167 'user_id': '${user_id}',
167 'user_id': '${user_id}',
168 })
168 })
169 c_obj.default_create_repo_group = RepoGroupModel() \
169 c_obj.default_create_repo_group = RepoGroupModel() \
170 .get_default_create_personal_repo_group()
170 .get_default_create_personal_repo_group()
171 c_obj.personal_repo_group_name = RepoGroupModel() \
171 c_obj.personal_repo_group_name = RepoGroupModel() \
172 .get_personal_group_name(DummyUser)
172 .get_personal_group_name(DummyUser)
173
173
174 @LoginRequired()
174 @LoginRequired()
175 @HasPermissionAllDecorator('hg.admin')
175 @HasPermissionAllDecorator('hg.admin')
176 def users_new(self):
176 def users_new(self):
177 _ = self.request.translate
177 _ = self.request.translate
178 c = self.load_default_context()
178 c = self.load_default_context()
179 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
179 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
180 self._set_personal_repo_group_template_vars(c)
180 self._set_personal_repo_group_template_vars(c)
181 return self._get_template_context(c)
181 return self._get_template_context(c)
182
182
183 @LoginRequired()
183 @LoginRequired()
184 @HasPermissionAllDecorator('hg.admin')
184 @HasPermissionAllDecorator('hg.admin')
185 @CSRFRequired()
185 @CSRFRequired()
186 def users_create(self):
186 def users_create(self):
187 _ = self.request.translate
187 _ = self.request.translate
188 c = self.load_default_context()
188 c = self.load_default_context()
189 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
189 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
190 user_model = UserModel()
190 user_model = UserModel()
191 user_form = UserForm(self.request.translate)()
191 user_form = UserForm(self.request.translate)()
192 try:
192 try:
193 form_result = user_form.to_python(dict(self.request.POST))
193 form_result = user_form.to_python(dict(self.request.POST))
194 user = user_model.create(form_result)
194 user = user_model.create(form_result)
195 Session().flush()
195 Session().flush()
196 creation_data = user.get_api_data()
196 creation_data = user.get_api_data()
197 username = form_result['username']
197 username = form_result['username']
198
198
199 audit_logger.store_web(
199 audit_logger.store_web(
200 'user.create', action_data={'data': creation_data},
200 'user.create', action_data={'data': creation_data},
201 user=c.rhodecode_user)
201 user=c.rhodecode_user)
202
202
203 user_link = h.link_to(
203 user_link = h.link_to(
204 h.escape(username),
204 h.escape(username),
205 h.route_path('user_edit', user_id=user.user_id))
205 h.route_path('user_edit', user_id=user.user_id))
206 h.flash(h.literal(_('Created user %(user_link)s')
206 h.flash(h.literal(_('Created user %(user_link)s')
207 % {'user_link': user_link}), category='success')
207 % {'user_link': user_link}), category='success')
208 Session().commit()
208 Session().commit()
209 except formencode.Invalid as errors:
209 except formencode.Invalid as errors:
210 self._set_personal_repo_group_template_vars(c)
210 self._set_personal_repo_group_template_vars(c)
211 data = render(
211 data = render(
212 'rhodecode:templates/admin/users/user_add.mako',
212 'rhodecode:templates/admin/users/user_add.mako',
213 self._get_template_context(c), self.request)
213 self._get_template_context(c), self.request)
214 html = formencode.htmlfill.render(
214 html = formencode.htmlfill.render(
215 data,
215 data,
216 defaults=errors.value,
216 defaults=errors.value,
217 errors=errors.unpack_errors() or {},
217 errors=errors.error_dict or {},
218 prefix_error=False,
218 prefix_error=False,
219 encoding="UTF-8",
219 encoding="UTF-8",
220 force_defaults=False
220 force_defaults=False
221 )
221 )
222 return Response(html)
222 return Response(html)
223 except UserCreationError as e:
223 except UserCreationError as e:
224 h.flash(safe_str(e), 'error')
224 h.flash(safe_str(e), 'error')
225 except Exception:
225 except Exception:
226 log.exception("Exception creation of user")
226 log.exception("Exception creation of user")
227 h.flash(_('Error occurred during creation of user %s')
227 h.flash(_('Error occurred during creation of user %s')
228 % self.request.POST.get('username'), category='error')
228 % self.request.POST.get('username'), category='error')
229 raise HTTPFound(h.route_path('users'))
229 raise HTTPFound(h.route_path('users'))
230
230
231
231
232 class UsersView(UserAppView):
232 class UsersView(UserAppView):
233 ALLOW_SCOPED_TOKENS = False
233 ALLOW_SCOPED_TOKENS = False
234 """
234 """
235 This view has alternative version inside EE, if modified please take a look
235 This view has alternative version inside EE, if modified please take a look
236 in there as well.
236 in there as well.
237 """
237 """
238
238
239 def get_auth_plugins(self):
239 def get_auth_plugins(self):
240 valid_plugins = []
240 valid_plugins = []
241 authn_registry = get_authn_registry(self.request.registry)
241 authn_registry = get_authn_registry(self.request.registry)
242 for plugin in authn_registry.get_plugins_for_authentication():
242 for plugin in authn_registry.get_plugins_for_authentication():
243 if isinstance(plugin, RhodeCodeExternalAuthPlugin):
243 if isinstance(plugin, RhodeCodeExternalAuthPlugin):
244 valid_plugins.append(plugin)
244 valid_plugins.append(plugin)
245 elif plugin.name == 'rhodecode':
245 elif plugin.name == 'rhodecode':
246 valid_plugins.append(plugin)
246 valid_plugins.append(plugin)
247
247
248 # extend our choices if user has set a bound plugin which isn't enabled at the
248 # extend our choices if user has set a bound plugin which isn't enabled at the
249 # moment
249 # moment
250 extern_type = self.db_user.extern_type
250 extern_type = self.db_user.extern_type
251 if extern_type not in [x.uid for x in valid_plugins]:
251 if extern_type not in [x.uid for x in valid_plugins]:
252 try:
252 try:
253 plugin = authn_registry.get_plugin_by_uid(extern_type)
253 plugin = authn_registry.get_plugin_by_uid(extern_type)
254 if plugin:
254 if plugin:
255 valid_plugins.append(plugin)
255 valid_plugins.append(plugin)
256
256
257 except Exception:
257 except Exception:
258 log.exception(
258 log.exception(
259 f'Could not extend user plugins with `{extern_type}`')
259 f'Could not extend user plugins with `{extern_type}`')
260 return valid_plugins
260 return valid_plugins
261
261
262 def load_default_context(self):
262 def load_default_context(self):
263 req = self.request
263 req = self.request
264
264
265 c = self._get_local_tmpl_context()
265 c = self._get_local_tmpl_context()
266 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
266 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
267 c.allowed_languages = [
267 c.allowed_languages = [
268 ('en', 'English (en)'),
268 ('en', 'English (en)'),
269 ('de', 'German (de)'),
269 ('de', 'German (de)'),
270 ('fr', 'French (fr)'),
270 ('fr', 'French (fr)'),
271 ('it', 'Italian (it)'),
271 ('it', 'Italian (it)'),
272 ('ja', 'Japanese (ja)'),
272 ('ja', 'Japanese (ja)'),
273 ('pl', 'Polish (pl)'),
273 ('pl', 'Polish (pl)'),
274 ('pt', 'Portuguese (pt)'),
274 ('pt', 'Portuguese (pt)'),
275 ('ru', 'Russian (ru)'),
275 ('ru', 'Russian (ru)'),
276 ('zh', 'Chinese (zh)'),
276 ('zh', 'Chinese (zh)'),
277 ]
277 ]
278
278
279 c.allowed_extern_types = [
279 c.allowed_extern_types = [
280 (x.uid, x.get_display_name()) for x in self.get_auth_plugins()
280 (x.uid, x.get_display_name()) for x in self.get_auth_plugins()
281 ]
281 ]
282 perms = req.registry.settings.get('available_permissions')
282 perms = req.registry.settings.get('available_permissions')
283 if not perms:
283 if not perms:
284 # inject info about available permissions
284 # inject info about available permissions
285 auth.set_available_permissions(req.registry.settings)
285 auth.set_available_permissions(req.registry.settings)
286
286
287 c.available_permissions = req.registry.settings['available_permissions']
287 c.available_permissions = req.registry.settings['available_permissions']
288 PermissionModel().set_global_permission_choices(
288 PermissionModel().set_global_permission_choices(
289 c, gettext_translator=req.translate)
289 c, gettext_translator=req.translate)
290
290
291 return c
291 return c
292
292
293 @LoginRequired()
293 @LoginRequired()
294 @HasPermissionAllDecorator('hg.admin')
294 @HasPermissionAllDecorator('hg.admin')
295 @CSRFRequired()
295 @CSRFRequired()
296 def user_update(self):
296 def user_update(self):
297 _ = self.request.translate
297 _ = self.request.translate
298 c = self.load_default_context()
298 c = self.load_default_context()
299
299
300 user_id = self.db_user_id
300 user_id = self.db_user_id
301 c.user = self.db_user
301 c.user = self.db_user
302
302
303 c.active = 'profile'
303 c.active = 'profile'
304 c.extern_type = c.user.extern_type
304 c.extern_type = c.user.extern_type
305 c.extern_name = c.user.extern_name
305 c.extern_name = c.user.extern_name
306 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
306 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
307 available_languages = [x[0] for x in c.allowed_languages]
307 available_languages = [x[0] for x in c.allowed_languages]
308 _form = UserForm(self.request.translate, edit=True,
308 _form = UserForm(self.request.translate, edit=True,
309 available_languages=available_languages,
309 available_languages=available_languages,
310 old_data={'user_id': user_id,
310 old_data={'user_id': user_id,
311 'email': c.user.email})()
311 'email': c.user.email})()
312
312
313 c.edit_mode = self.request.POST.get('edit') == '1'
313 c.edit_mode = self.request.POST.get('edit') == '1'
314 form_result = {}
314 form_result = {}
315 old_values = c.user.get_api_data()
315 old_values = c.user.get_api_data()
316 try:
316 try:
317 form_result = _form.to_python(dict(self.request.POST))
317 form_result = _form.to_python(dict(self.request.POST))
318 skip_attrs = ['extern_name']
318 skip_attrs = ['extern_name']
319 # TODO: plugin should define if username can be updated
319 # TODO: plugin should define if username can be updated
320
320
321 if c.extern_type != "rhodecode" and not c.edit_mode:
321 if c.extern_type != "rhodecode" and not c.edit_mode:
322 # forbid updating username for external accounts
322 # forbid updating username for external accounts
323 skip_attrs.append('username')
323 skip_attrs.append('username')
324
324
325 UserModel().update_user(
325 UserModel().update_user(
326 user_id, skip_attrs=skip_attrs, **form_result)
326 user_id, skip_attrs=skip_attrs, **form_result)
327
327
328 audit_logger.store_web(
328 audit_logger.store_web(
329 'user.edit', action_data={'old_data': old_values},
329 'user.edit', action_data={'old_data': old_values},
330 user=c.rhodecode_user)
330 user=c.rhodecode_user)
331
331
332 Session().commit()
332 Session().commit()
333 h.flash(_('User updated successfully'), category='success')
333 h.flash(_('User updated successfully'), category='success')
334 except formencode.Invalid as errors:
334 except formencode.Invalid as errors:
335 data = render(
335 data = render(
336 'rhodecode:templates/admin/users/user_edit.mako',
336 'rhodecode:templates/admin/users/user_edit.mako',
337 self._get_template_context(c), self.request)
337 self._get_template_context(c), self.request)
338 html = formencode.htmlfill.render(
338 html = formencode.htmlfill.render(
339 data,
339 data,
340 defaults=errors.value,
340 defaults=errors.value,
341 errors=errors.unpack_errors() or {},
341 errors=errors.unpack_errors() or {},
342 prefix_error=False,
342 prefix_error=False,
343 encoding="UTF-8",
343 encoding="UTF-8",
344 force_defaults=False
344 force_defaults=False
345 )
345 )
346 return Response(html)
346 return Response(html)
347 except UserCreationError as e:
347 except UserCreationError as e:
348 h.flash(safe_str(e), 'error')
348 h.flash(safe_str(e), 'error')
349 except Exception:
349 except Exception:
350 log.exception("Exception updating user")
350 log.exception("Exception updating user")
351 h.flash(_('Error occurred during update of user %s')
351 h.flash(_('Error occurred during update of user %s')
352 % form_result.get('username'), category='error')
352 % form_result.get('username'), category='error')
353 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
353 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
354
354
355 @LoginRequired()
355 @LoginRequired()
356 @HasPermissionAllDecorator('hg.admin')
356 @HasPermissionAllDecorator('hg.admin')
357 @CSRFRequired()
357 @CSRFRequired()
358 def user_delete(self):
358 def user_delete(self):
359 _ = self.request.translate
359 _ = self.request.translate
360 c = self.load_default_context()
360 c = self.load_default_context()
361 c.user = self.db_user
361 c.user = self.db_user
362
362
363 _repos = len(c.user.repositories)
363 _repos = len(c.user.repositories)
364 _repo_groups = len(c.user.repository_groups)
364 _repo_groups = len(c.user.repository_groups)
365 _user_groups = len(c.user.user_groups)
365 _user_groups = len(c.user.user_groups)
366 _pull_requests = len(c.user.user_pull_requests)
366 _pull_requests = len(c.user.user_pull_requests)
367 _artifacts = len(c.user.artifacts)
367 _artifacts = len(c.user.artifacts)
368
368
369 handle_repos = None
369 handle_repos = None
370 handle_repo_groups = None
370 handle_repo_groups = None
371 handle_user_groups = None
371 handle_user_groups = None
372 handle_pull_requests = None
372 handle_pull_requests = None
373 handle_artifacts = None
373 handle_artifacts = None
374
374
375 # calls for flash of handle based on handle case detach or delete
375 # calls for flash of handle based on handle case detach or delete
376 def set_handle_flash_repos():
376 def set_handle_flash_repos():
377 handle = handle_repos
377 handle = handle_repos
378 if handle == 'detach':
378 if handle == 'detach':
379 h.flash(_('Detached %s repositories') % _repos,
379 h.flash(_('Detached %s repositories') % _repos,
380 category='success')
380 category='success')
381 elif handle == 'delete':
381 elif handle == 'delete':
382 h.flash(_('Deleted %s repositories') % _repos,
382 h.flash(_('Deleted %s repositories') % _repos,
383 category='success')
383 category='success')
384
384
385 def set_handle_flash_repo_groups():
385 def set_handle_flash_repo_groups():
386 handle = handle_repo_groups
386 handle = handle_repo_groups
387 if handle == 'detach':
387 if handle == 'detach':
388 h.flash(_('Detached %s repository groups') % _repo_groups,
388 h.flash(_('Detached %s repository groups') % _repo_groups,
389 category='success')
389 category='success')
390 elif handle == 'delete':
390 elif handle == 'delete':
391 h.flash(_('Deleted %s repository groups') % _repo_groups,
391 h.flash(_('Deleted %s repository groups') % _repo_groups,
392 category='success')
392 category='success')
393
393
394 def set_handle_flash_user_groups():
394 def set_handle_flash_user_groups():
395 handle = handle_user_groups
395 handle = handle_user_groups
396 if handle == 'detach':
396 if handle == 'detach':
397 h.flash(_('Detached %s user groups') % _user_groups,
397 h.flash(_('Detached %s user groups') % _user_groups,
398 category='success')
398 category='success')
399 elif handle == 'delete':
399 elif handle == 'delete':
400 h.flash(_('Deleted %s user groups') % _user_groups,
400 h.flash(_('Deleted %s user groups') % _user_groups,
401 category='success')
401 category='success')
402
402
403 def set_handle_flash_pull_requests():
403 def set_handle_flash_pull_requests():
404 handle = handle_pull_requests
404 handle = handle_pull_requests
405 if handle == 'detach':
405 if handle == 'detach':
406 h.flash(_('Detached %s pull requests') % _pull_requests,
406 h.flash(_('Detached %s pull requests') % _pull_requests,
407 category='success')
407 category='success')
408 elif handle == 'delete':
408 elif handle == 'delete':
409 h.flash(_('Deleted %s pull requests') % _pull_requests,
409 h.flash(_('Deleted %s pull requests') % _pull_requests,
410 category='success')
410 category='success')
411
411
412 def set_handle_flash_artifacts():
412 def set_handle_flash_artifacts():
413 handle = handle_artifacts
413 handle = handle_artifacts
414 if handle == 'detach':
414 if handle == 'detach':
415 h.flash(_('Detached %s artifacts') % _artifacts,
415 h.flash(_('Detached %s artifacts') % _artifacts,
416 category='success')
416 category='success')
417 elif handle == 'delete':
417 elif handle == 'delete':
418 h.flash(_('Deleted %s artifacts') % _artifacts,
418 h.flash(_('Deleted %s artifacts') % _artifacts,
419 category='success')
419 category='success')
420
420
421 handle_user = User.get_first_super_admin()
421 handle_user = User.get_first_super_admin()
422 handle_user_id = safe_int(self.request.POST.get('detach_user_id'))
422 handle_user_id = safe_int(self.request.POST.get('detach_user_id'))
423 if handle_user_id:
423 if handle_user_id:
424 # NOTE(marcink): we get new owner for objects...
424 # NOTE(marcink): we get new owner for objects...
425 handle_user = User.get_or_404(handle_user_id)
425 handle_user = User.get_or_404(handle_user_id)
426
426
427 if _repos and self.request.POST.get('user_repos'):
427 if _repos and self.request.POST.get('user_repos'):
428 handle_repos = self.request.POST['user_repos']
428 handle_repos = self.request.POST['user_repos']
429
429
430 if _repo_groups and self.request.POST.get('user_repo_groups'):
430 if _repo_groups and self.request.POST.get('user_repo_groups'):
431 handle_repo_groups = self.request.POST['user_repo_groups']
431 handle_repo_groups = self.request.POST['user_repo_groups']
432
432
433 if _user_groups and self.request.POST.get('user_user_groups'):
433 if _user_groups and self.request.POST.get('user_user_groups'):
434 handle_user_groups = self.request.POST['user_user_groups']
434 handle_user_groups = self.request.POST['user_user_groups']
435
435
436 if _pull_requests and self.request.POST.get('user_pull_requests'):
436 if _pull_requests and self.request.POST.get('user_pull_requests'):
437 handle_pull_requests = self.request.POST['user_pull_requests']
437 handle_pull_requests = self.request.POST['user_pull_requests']
438
438
439 if _artifacts and self.request.POST.get('user_artifacts'):
439 if _artifacts and self.request.POST.get('user_artifacts'):
440 handle_artifacts = self.request.POST['user_artifacts']
440 handle_artifacts = self.request.POST['user_artifacts']
441
441
442 old_values = c.user.get_api_data()
442 old_values = c.user.get_api_data()
443
443
444 try:
444 try:
445
445
446 UserModel().delete(
446 UserModel().delete(
447 c.user,
447 c.user,
448 handle_repos=handle_repos,
448 handle_repos=handle_repos,
449 handle_repo_groups=handle_repo_groups,
449 handle_repo_groups=handle_repo_groups,
450 handle_user_groups=handle_user_groups,
450 handle_user_groups=handle_user_groups,
451 handle_pull_requests=handle_pull_requests,
451 handle_pull_requests=handle_pull_requests,
452 handle_artifacts=handle_artifacts,
452 handle_artifacts=handle_artifacts,
453 handle_new_owner=handle_user
453 handle_new_owner=handle_user
454 )
454 )
455
455
456 audit_logger.store_web(
456 audit_logger.store_web(
457 'user.delete', action_data={'old_data': old_values},
457 'user.delete', action_data={'old_data': old_values},
458 user=c.rhodecode_user)
458 user=c.rhodecode_user)
459
459
460 Session().commit()
460 Session().commit()
461 set_handle_flash_repos()
461 set_handle_flash_repos()
462 set_handle_flash_repo_groups()
462 set_handle_flash_repo_groups()
463 set_handle_flash_user_groups()
463 set_handle_flash_user_groups()
464 set_handle_flash_pull_requests()
464 set_handle_flash_pull_requests()
465 set_handle_flash_artifacts()
465 set_handle_flash_artifacts()
466 username = h.escape(old_values['username'])
466 username = h.escape(old_values['username'])
467 h.flash(_('Successfully deleted user `{}`').format(username), category='success')
467 h.flash(_('Successfully deleted user `{}`').format(username), category='success')
468 except (UserOwnsReposException, UserOwnsRepoGroupsException,
468 except (UserOwnsReposException, UserOwnsRepoGroupsException,
469 UserOwnsUserGroupsException, UserOwnsPullRequestsException,
469 UserOwnsUserGroupsException, UserOwnsPullRequestsException,
470 UserOwnsArtifactsException, DefaultUserException) as e:
470 UserOwnsArtifactsException, DefaultUserException) as e:
471
471
472 h.flash(safe_str(e), category='warning')
472 h.flash(safe_str(e), category='warning')
473 except Exception:
473 except Exception:
474 log.exception("Exception during deletion of user")
474 log.exception("Exception during deletion of user")
475 h.flash(_('An error occurred during deletion of user'),
475 h.flash(_('An error occurred during deletion of user'),
476 category='error')
476 category='error')
477 raise HTTPFound(h.route_path('users'))
477 raise HTTPFound(h.route_path('users'))
478
478
479 @LoginRequired()
479 @LoginRequired()
480 @HasPermissionAllDecorator('hg.admin')
480 @HasPermissionAllDecorator('hg.admin')
481 def user_edit(self):
481 def user_edit(self):
482 _ = self.request.translate
482 _ = self.request.translate
483 c = self.load_default_context()
483 c = self.load_default_context()
484 c.user = self.db_user
484 c.user = self.db_user
485
485
486 c.active = 'profile'
486 c.active = 'profile'
487 c.extern_type = c.user.extern_type
487 c.extern_type = c.user.extern_type
488 c.extern_name = c.user.extern_name
488 c.extern_name = c.user.extern_name
489 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
489 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
490 c.edit_mode = self.request.GET.get('edit') == '1'
490 c.edit_mode = self.request.GET.get('edit') == '1'
491
491
492 defaults = c.user.get_dict()
492 defaults = c.user.get_dict()
493 defaults.update({'language': c.user.user_data.get('language')})
493 defaults.update({'language': c.user.user_data.get('language')})
494
494
495 data = render(
495 data = render(
496 'rhodecode:templates/admin/users/user_edit.mako',
496 'rhodecode:templates/admin/users/user_edit.mako',
497 self._get_template_context(c), self.request)
497 self._get_template_context(c), self.request)
498 html = formencode.htmlfill.render(
498 html = formencode.htmlfill.render(
499 data,
499 data,
500 defaults=defaults,
500 defaults=defaults,
501 encoding="UTF-8",
501 encoding="UTF-8",
502 force_defaults=False
502 force_defaults=False
503 )
503 )
504 return Response(html)
504 return Response(html)
505
505
506 @LoginRequired()
506 @LoginRequired()
507 @HasPermissionAllDecorator('hg.admin')
507 @HasPermissionAllDecorator('hg.admin')
508 def user_edit_advanced(self):
508 def user_edit_advanced(self):
509 _ = self.request.translate
509 _ = self.request.translate
510 c = self.load_default_context()
510 c = self.load_default_context()
511
511
512 user_id = self.db_user_id
512 user_id = self.db_user_id
513 c.user = self.db_user
513 c.user = self.db_user
514
514
515 c.detach_user = User.get_first_super_admin()
515 c.detach_user = User.get_first_super_admin()
516 detach_user_id = safe_int(self.request.GET.get('detach_user_id'))
516 detach_user_id = safe_int(self.request.GET.get('detach_user_id'))
517 if detach_user_id:
517 if detach_user_id:
518 c.detach_user = User.get_or_404(detach_user_id)
518 c.detach_user = User.get_or_404(detach_user_id)
519
519
520 c.active = 'advanced'
520 c.active = 'advanced'
521 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
521 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
522 c.personal_repo_group_name = RepoGroupModel()\
522 c.personal_repo_group_name = RepoGroupModel()\
523 .get_personal_group_name(c.user)
523 .get_personal_group_name(c.user)
524
524
525 c.user_to_review_rules = sorted(
525 c.user_to_review_rules = sorted(
526 (x.user for x in c.user.user_review_rules),
526 (x.user for x in c.user.user_review_rules),
527 key=lambda u: u.username.lower())
527 key=lambda u: u.username.lower())
528
528
529 defaults = c.user.get_dict()
529 defaults = c.user.get_dict()
530
530
531 # Interim workaround if the user participated on any pull requests as a
531 # Interim workaround if the user participated on any pull requests as a
532 # reviewer.
532 # reviewer.
533 has_review = len(c.user.reviewer_pull_requests)
533 has_review = len(c.user.reviewer_pull_requests)
534 c.can_delete_user = not has_review
534 c.can_delete_user = not has_review
535 c.can_delete_user_message = ''
535 c.can_delete_user_message = ''
536 inactive_link = h.link_to(
536 inactive_link = h.link_to(
537 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
537 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
538 if has_review == 1:
538 if has_review == 1:
539 c.can_delete_user_message = h.literal(_(
539 c.can_delete_user_message = h.literal(_(
540 'The user participates as reviewer in {} pull request and '
540 'The user participates as reviewer in {} pull request and '
541 'cannot be deleted. \nYou can set the user to '
541 'cannot be deleted. \nYou can set the user to '
542 '"{}" instead of deleting it.').format(
542 '"{}" instead of deleting it.').format(
543 has_review, inactive_link))
543 has_review, inactive_link))
544 elif has_review:
544 elif has_review:
545 c.can_delete_user_message = h.literal(_(
545 c.can_delete_user_message = h.literal(_(
546 'The user participates as reviewer in {} pull requests and '
546 'The user participates as reviewer in {} pull requests and '
547 'cannot be deleted. \nYou can set the user to '
547 'cannot be deleted. \nYou can set the user to '
548 '"{}" instead of deleting it.').format(
548 '"{}" instead of deleting it.').format(
549 has_review, inactive_link))
549 has_review, inactive_link))
550
550
551 data = render(
551 data = render(
552 'rhodecode:templates/admin/users/user_edit.mako',
552 'rhodecode:templates/admin/users/user_edit.mako',
553 self._get_template_context(c), self.request)
553 self._get_template_context(c), self.request)
554 html = formencode.htmlfill.render(
554 html = formencode.htmlfill.render(
555 data,
555 data,
556 defaults=defaults,
556 defaults=defaults,
557 encoding="UTF-8",
557 encoding="UTF-8",
558 force_defaults=False
558 force_defaults=False
559 )
559 )
560 return Response(html)
560 return Response(html)
561
561
562 @LoginRequired()
562 @LoginRequired()
563 @HasPermissionAllDecorator('hg.admin')
563 @HasPermissionAllDecorator('hg.admin')
564 def user_edit_global_perms(self):
564 def user_edit_global_perms(self):
565 _ = self.request.translate
565 _ = self.request.translate
566 c = self.load_default_context()
566 c = self.load_default_context()
567 c.user = self.db_user
567 c.user = self.db_user
568
568
569 c.active = 'global_perms'
569 c.active = 'global_perms'
570
570
571 c.default_user = User.get_default_user()
571 c.default_user = User.get_default_user()
572 defaults = c.user.get_dict()
572 defaults = c.user.get_dict()
573 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
573 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
574 defaults.update(c.default_user.get_default_perms())
574 defaults.update(c.default_user.get_default_perms())
575 defaults.update(c.user.get_default_perms())
575 defaults.update(c.user.get_default_perms())
576
576
577 data = render(
577 data = render(
578 'rhodecode:templates/admin/users/user_edit.mako',
578 'rhodecode:templates/admin/users/user_edit.mako',
579 self._get_template_context(c), self.request)
579 self._get_template_context(c), self.request)
580 html = formencode.htmlfill.render(
580 html = formencode.htmlfill.render(
581 data,
581 data,
582 defaults=defaults,
582 defaults=defaults,
583 encoding="UTF-8",
583 encoding="UTF-8",
584 force_defaults=False
584 force_defaults=False
585 )
585 )
586 return Response(html)
586 return Response(html)
587
587
588 @LoginRequired()
588 @LoginRequired()
589 @HasPermissionAllDecorator('hg.admin')
589 @HasPermissionAllDecorator('hg.admin')
590 @CSRFRequired()
590 @CSRFRequired()
591 def user_edit_global_perms_update(self):
591 def user_edit_global_perms_update(self):
592 _ = self.request.translate
592 _ = self.request.translate
593 c = self.load_default_context()
593 c = self.load_default_context()
594
594
595 user_id = self.db_user_id
595 user_id = self.db_user_id
596 c.user = self.db_user
596 c.user = self.db_user
597
597
598 c.active = 'global_perms'
598 c.active = 'global_perms'
599 try:
599 try:
600 # first stage that verifies the checkbox
600 # first stage that verifies the checkbox
601 _form = UserIndividualPermissionsForm(self.request.translate)
601 _form = UserIndividualPermissionsForm(self.request.translate)
602 form_result = _form.to_python(dict(self.request.POST))
602 form_result = _form.to_python(dict(self.request.POST))
603 inherit_perms = form_result['inherit_default_permissions']
603 inherit_perms = form_result['inherit_default_permissions']
604 c.user.inherit_default_permissions = inherit_perms
604 c.user.inherit_default_permissions = inherit_perms
605 Session().add(c.user)
605 Session().add(c.user)
606
606
607 if not inherit_perms:
607 if not inherit_perms:
608 # only update the individual ones if we un check the flag
608 # only update the individual ones if we un check the flag
609 _form = UserPermissionsForm(
609 _form = UserPermissionsForm(
610 self.request.translate,
610 self.request.translate,
611 [x[0] for x in c.repo_create_choices],
611 [x[0] for x in c.repo_create_choices],
612 [x[0] for x in c.repo_create_on_write_choices],
612 [x[0] for x in c.repo_create_on_write_choices],
613 [x[0] for x in c.repo_group_create_choices],
613 [x[0] for x in c.repo_group_create_choices],
614 [x[0] for x in c.user_group_create_choices],
614 [x[0] for x in c.user_group_create_choices],
615 [x[0] for x in c.fork_choices],
615 [x[0] for x in c.fork_choices],
616 [x[0] for x in c.inherit_default_permission_choices])()
616 [x[0] for x in c.inherit_default_permission_choices])()
617
617
618 form_result = _form.to_python(dict(self.request.POST))
618 form_result = _form.to_python(dict(self.request.POST))
619 form_result.update({'perm_user_id': c.user.user_id})
619 form_result.update({'perm_user_id': c.user.user_id})
620
620
621 PermissionModel().update_user_permissions(form_result)
621 PermissionModel().update_user_permissions(form_result)
622
622
623 # TODO(marcink): implement global permissions
623 # TODO(marcink): implement global permissions
624 # audit_log.store_web('user.edit.permissions')
624 # audit_log.store_web('user.edit.permissions')
625
625
626 Session().commit()
626 Session().commit()
627
627
628 h.flash(_('User global permissions updated successfully'),
628 h.flash(_('User global permissions updated successfully'),
629 category='success')
629 category='success')
630
630
631 except formencode.Invalid as errors:
631 except formencode.Invalid as errors:
632 data = render(
632 data = render(
633 'rhodecode:templates/admin/users/user_edit.mako',
633 'rhodecode:templates/admin/users/user_edit.mako',
634 self._get_template_context(c), self.request)
634 self._get_template_context(c), self.request)
635 html = formencode.htmlfill.render(
635 html = formencode.htmlfill.render(
636 data,
636 data,
637 defaults=errors.value,
637 defaults=errors.value,
638 errors=errors.unpack_errors() or {},
638 errors=errors.unpack_errors() or {},
639 prefix_error=False,
639 prefix_error=False,
640 encoding="UTF-8",
640 encoding="UTF-8",
641 force_defaults=False
641 force_defaults=False
642 )
642 )
643 return Response(html)
643 return Response(html)
644 except Exception:
644 except Exception:
645 log.exception("Exception during permissions saving")
645 log.exception("Exception during permissions saving")
646 h.flash(_('An error occurred during permissions saving'),
646 h.flash(_('An error occurred during permissions saving'),
647 category='error')
647 category='error')
648
648
649 affected_user_ids = [user_id]
649 affected_user_ids = [user_id]
650 PermissionModel().trigger_permission_flush(affected_user_ids)
650 PermissionModel().trigger_permission_flush(affected_user_ids)
651 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
651 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
652
652
653 @LoginRequired()
653 @LoginRequired()
654 @HasPermissionAllDecorator('hg.admin')
654 @HasPermissionAllDecorator('hg.admin')
655 @CSRFRequired()
655 @CSRFRequired()
656 def user_enable_force_password_reset(self):
656 def user_enable_force_password_reset(self):
657 _ = self.request.translate
657 _ = self.request.translate
658 c = self.load_default_context()
658 c = self.load_default_context()
659
659
660 user_id = self.db_user_id
660 user_id = self.db_user_id
661 c.user = self.db_user
661 c.user = self.db_user
662
662
663 try:
663 try:
664 c.user.update_userdata(force_password_change=True)
664 c.user.update_userdata(force_password_change=True)
665
665
666 msg = _('Force password change enabled for user')
666 msg = _('Force password change enabled for user')
667 audit_logger.store_web('user.edit.password_reset.enabled',
667 audit_logger.store_web('user.edit.password_reset.enabled',
668 user=c.rhodecode_user)
668 user=c.rhodecode_user)
669
669
670 Session().commit()
670 Session().commit()
671 h.flash(msg, category='success')
671 h.flash(msg, category='success')
672 except Exception:
672 except Exception:
673 log.exception("Exception during password reset for user")
673 log.exception("Exception during password reset for user")
674 h.flash(_('An error occurred during password reset for user'),
674 h.flash(_('An error occurred during password reset for user'),
675 category='error')
675 category='error')
676
676
677 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
677 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
678
678
679 @LoginRequired()
679 @LoginRequired()
680 @HasPermissionAllDecorator('hg.admin')
680 @HasPermissionAllDecorator('hg.admin')
681 @CSRFRequired()
681 @CSRFRequired()
682 def user_disable_force_password_reset(self):
682 def user_disable_force_password_reset(self):
683 _ = self.request.translate
683 _ = self.request.translate
684 c = self.load_default_context()
684 c = self.load_default_context()
685
685
686 user_id = self.db_user_id
686 user_id = self.db_user_id
687 c.user = self.db_user
687 c.user = self.db_user
688
688
689 try:
689 try:
690 c.user.update_userdata(force_password_change=False)
690 c.user.update_userdata(force_password_change=False)
691
691
692 msg = _('Force password change disabled for user')
692 msg = _('Force password change disabled for user')
693 audit_logger.store_web(
693 audit_logger.store_web(
694 'user.edit.password_reset.disabled',
694 'user.edit.password_reset.disabled',
695 user=c.rhodecode_user)
695 user=c.rhodecode_user)
696
696
697 Session().commit()
697 Session().commit()
698 h.flash(msg, category='success')
698 h.flash(msg, category='success')
699 except Exception:
699 except Exception:
700 log.exception("Exception during password reset for user")
700 log.exception("Exception during password reset for user")
701 h.flash(_('An error occurred during password reset for user'),
701 h.flash(_('An error occurred during password reset for user'),
702 category='error')
702 category='error')
703
703
704 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
704 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
705
705
706 @LoginRequired()
706 @LoginRequired()
707 @HasPermissionAllDecorator('hg.admin')
707 @HasPermissionAllDecorator('hg.admin')
708 @CSRFRequired()
708 @CSRFRequired()
709 def user_notice_dismiss(self):
709 def user_notice_dismiss(self):
710 _ = self.request.translate
710 _ = self.request.translate
711 c = self.load_default_context()
711 c = self.load_default_context()
712
712
713 user_id = self.db_user_id
713 user_id = self.db_user_id
714 c.user = self.db_user
714 c.user = self.db_user
715 user_notice_id = safe_int(self.request.POST.get('notice_id'))
715 user_notice_id = safe_int(self.request.POST.get('notice_id'))
716 notice = UserNotice().query()\
716 notice = UserNotice().query()\
717 .filter(UserNotice.user_id == user_id)\
717 .filter(UserNotice.user_id == user_id)\
718 .filter(UserNotice.user_notice_id == user_notice_id)\
718 .filter(UserNotice.user_notice_id == user_notice_id)\
719 .scalar()
719 .scalar()
720 read = False
720 read = False
721 if notice:
721 if notice:
722 notice.notice_read = True
722 notice.notice_read = True
723 Session().add(notice)
723 Session().add(notice)
724 Session().commit()
724 Session().commit()
725 read = True
725 read = True
726
726
727 return {'notice': user_notice_id, 'read': read}
727 return {'notice': user_notice_id, 'read': read}
728
728
729 @LoginRequired()
729 @LoginRequired()
730 @HasPermissionAllDecorator('hg.admin')
730 @HasPermissionAllDecorator('hg.admin')
731 @CSRFRequired()
731 @CSRFRequired()
732 def user_create_personal_repo_group(self):
732 def user_create_personal_repo_group(self):
733 """
733 """
734 Create personal repository group for this user
734 Create personal repository group for this user
735 """
735 """
736 from rhodecode.model.repo_group import RepoGroupModel
736 from rhodecode.model.repo_group import RepoGroupModel
737
737
738 _ = self.request.translate
738 _ = self.request.translate
739 c = self.load_default_context()
739 c = self.load_default_context()
740
740
741 user_id = self.db_user_id
741 user_id = self.db_user_id
742 c.user = self.db_user
742 c.user = self.db_user
743
743
744 personal_repo_group = RepoGroup.get_user_personal_repo_group(
744 personal_repo_group = RepoGroup.get_user_personal_repo_group(
745 c.user.user_id)
745 c.user.user_id)
746 if personal_repo_group:
746 if personal_repo_group:
747 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
747 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
748
748
749 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
749 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
750 named_personal_group = RepoGroup.get_by_group_name(
750 named_personal_group = RepoGroup.get_by_group_name(
751 personal_repo_group_name)
751 personal_repo_group_name)
752 try:
752 try:
753
753
754 if named_personal_group and named_personal_group.user_id == c.user.user_id:
754 if named_personal_group and named_personal_group.user_id == c.user.user_id:
755 # migrate the same named group, and mark it as personal
755 # migrate the same named group, and mark it as personal
756 named_personal_group.personal = True
756 named_personal_group.personal = True
757 Session().add(named_personal_group)
757 Session().add(named_personal_group)
758 Session().commit()
758 Session().commit()
759 msg = _('Linked repository group `{}` as personal'.format(
759 msg = _('Linked repository group `{}` as personal'.format(
760 personal_repo_group_name))
760 personal_repo_group_name))
761 h.flash(msg, category='success')
761 h.flash(msg, category='success')
762 elif not named_personal_group:
762 elif not named_personal_group:
763 RepoGroupModel().create_personal_repo_group(c.user)
763 RepoGroupModel().create_personal_repo_group(c.user)
764
764
765 msg = _('Created repository group `{}`'.format(
765 msg = _('Created repository group `{}`'.format(
766 personal_repo_group_name))
766 personal_repo_group_name))
767 h.flash(msg, category='success')
767 h.flash(msg, category='success')
768 else:
768 else:
769 msg = _('Repository group `{}` is already taken'.format(
769 msg = _('Repository group `{}` is already taken'.format(
770 personal_repo_group_name))
770 personal_repo_group_name))
771 h.flash(msg, category='warning')
771 h.flash(msg, category='warning')
772 except Exception:
772 except Exception:
773 log.exception("Exception during repository group creation")
773 log.exception("Exception during repository group creation")
774 msg = _(
774 msg = _(
775 'An error occurred during repository group creation for user')
775 'An error occurred during repository group creation for user')
776 h.flash(msg, category='error')
776 h.flash(msg, category='error')
777 Session().rollback()
777 Session().rollback()
778
778
779 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
779 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
780
780
781 @LoginRequired()
781 @LoginRequired()
782 @HasPermissionAllDecorator('hg.admin')
782 @HasPermissionAllDecorator('hg.admin')
783 def auth_tokens(self):
783 def auth_tokens(self):
784 _ = self.request.translate
784 _ = self.request.translate
785 c = self.load_default_context()
785 c = self.load_default_context()
786 c.user = self.db_user
786 c.user = self.db_user
787
787
788 c.active = 'auth_tokens'
788 c.active = 'auth_tokens'
789
789
790 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
790 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
791 c.role_values = [
791 c.role_values = [
792 (x, AuthTokenModel.cls._get_role_name(x))
792 (x, AuthTokenModel.cls._get_role_name(x))
793 for x in AuthTokenModel.cls.ROLES]
793 for x in AuthTokenModel.cls.ROLES]
794 c.role_options = [(c.role_values, _("Role"))]
794 c.role_options = [(c.role_values, _("Role"))]
795 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
795 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
796 c.user.user_id, show_expired=True)
796 c.user.user_id, show_expired=True)
797 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
797 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
798 return self._get_template_context(c)
798 return self._get_template_context(c)
799
799
800 @LoginRequired()
800 @LoginRequired()
801 @HasPermissionAllDecorator('hg.admin')
801 @HasPermissionAllDecorator('hg.admin')
802 def auth_tokens_view(self):
802 def auth_tokens_view(self):
803 _ = self.request.translate
803 _ = self.request.translate
804 c = self.load_default_context()
804 c = self.load_default_context()
805 c.user = self.db_user
805 c.user = self.db_user
806
806
807 auth_token_id = self.request.POST.get('auth_token_id')
807 auth_token_id = self.request.POST.get('auth_token_id')
808
808
809 if auth_token_id:
809 if auth_token_id:
810 token = UserApiKeys.get_or_404(auth_token_id)
810 token = UserApiKeys.get_or_404(auth_token_id)
811
811
812 return {
812 return {
813 'auth_token': token.api_key
813 'auth_token': token.api_key
814 }
814 }
815
815
816 def maybe_attach_token_scope(self, token):
816 def maybe_attach_token_scope(self, token):
817 # implemented in EE edition
817 # implemented in EE edition
818 pass
818 pass
819
819
820 @LoginRequired()
820 @LoginRequired()
821 @HasPermissionAllDecorator('hg.admin')
821 @HasPermissionAllDecorator('hg.admin')
822 @CSRFRequired()
822 @CSRFRequired()
823 def auth_tokens_add(self):
823 def auth_tokens_add(self):
824 _ = self.request.translate
824 _ = self.request.translate
825 c = self.load_default_context()
825 c = self.load_default_context()
826
826
827 user_id = self.db_user_id
827 user_id = self.db_user_id
828 c.user = self.db_user
828 c.user = self.db_user
829
829
830 user_data = c.user.get_api_data()
830 user_data = c.user.get_api_data()
831 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
831 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
832 description = self.request.POST.get('description')
832 description = self.request.POST.get('description')
833 role = self.request.POST.get('role')
833 role = self.request.POST.get('role')
834
834
835 token = UserModel().add_auth_token(
835 token = UserModel().add_auth_token(
836 user=c.user.user_id,
836 user=c.user.user_id,
837 lifetime_minutes=lifetime, role=role, description=description,
837 lifetime_minutes=lifetime, role=role, description=description,
838 scope_callback=self.maybe_attach_token_scope)
838 scope_callback=self.maybe_attach_token_scope)
839 token_data = token.get_api_data()
839 token_data = token.get_api_data()
840
840
841 audit_logger.store_web(
841 audit_logger.store_web(
842 'user.edit.token.add', action_data={
842 'user.edit.token.add', action_data={
843 'data': {'token': token_data, 'user': user_data}},
843 'data': {'token': token_data, 'user': user_data}},
844 user=self._rhodecode_user, )
844 user=self._rhodecode_user, )
845 Session().commit()
845 Session().commit()
846
846
847 h.flash(_("Auth token successfully created"), category='success')
847 h.flash(_("Auth token successfully created"), category='success')
848 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
848 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
849
849
850 @LoginRequired()
850 @LoginRequired()
851 @HasPermissionAllDecorator('hg.admin')
851 @HasPermissionAllDecorator('hg.admin')
852 @CSRFRequired()
852 @CSRFRequired()
853 def auth_tokens_delete(self):
853 def auth_tokens_delete(self):
854 _ = self.request.translate
854 _ = self.request.translate
855 c = self.load_default_context()
855 c = self.load_default_context()
856
856
857 user_id = self.db_user_id
857 user_id = self.db_user_id
858 c.user = self.db_user
858 c.user = self.db_user
859
859
860 user_data = c.user.get_api_data()
860 user_data = c.user.get_api_data()
861
861
862 del_auth_token = self.request.POST.get('del_auth_token')
862 del_auth_token = self.request.POST.get('del_auth_token')
863
863
864 if del_auth_token:
864 if del_auth_token:
865 token = UserApiKeys.get_or_404(del_auth_token)
865 token = UserApiKeys.get_or_404(del_auth_token)
866 token_data = token.get_api_data()
866 token_data = token.get_api_data()
867
867
868 AuthTokenModel().delete(del_auth_token, c.user.user_id)
868 AuthTokenModel().delete(del_auth_token, c.user.user_id)
869 audit_logger.store_web(
869 audit_logger.store_web(
870 'user.edit.token.delete', action_data={
870 'user.edit.token.delete', action_data={
871 'data': {'token': token_data, 'user': user_data}},
871 'data': {'token': token_data, 'user': user_data}},
872 user=self._rhodecode_user,)
872 user=self._rhodecode_user,)
873 Session().commit()
873 Session().commit()
874 h.flash(_("Auth token successfully deleted"), category='success')
874 h.flash(_("Auth token successfully deleted"), category='success')
875
875
876 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
876 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
877
877
878 @LoginRequired()
878 @LoginRequired()
879 @HasPermissionAllDecorator('hg.admin')
879 @HasPermissionAllDecorator('hg.admin')
880 def ssh_keys(self):
880 def ssh_keys(self):
881 _ = self.request.translate
881 _ = self.request.translate
882 c = self.load_default_context()
882 c = self.load_default_context()
883 c.user = self.db_user
883 c.user = self.db_user
884
884
885 c.active = 'ssh_keys'
885 c.active = 'ssh_keys'
886 c.default_key = self.request.GET.get('default_key')
886 c.default_key = self.request.GET.get('default_key')
887 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
887 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
888 return self._get_template_context(c)
888 return self._get_template_context(c)
889
889
890 @LoginRequired()
890 @LoginRequired()
891 @HasPermissionAllDecorator('hg.admin')
891 @HasPermissionAllDecorator('hg.admin')
892 def ssh_keys_generate_keypair(self):
892 def ssh_keys_generate_keypair(self):
893 _ = self.request.translate
893 _ = self.request.translate
894 c = self.load_default_context()
894 c = self.load_default_context()
895
895
896 c.user = self.db_user
896 c.user = self.db_user
897
897
898 c.active = 'ssh_keys_generate'
898 c.active = 'ssh_keys_generate'
899 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
899 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
900 private_format = self.request.GET.get('private_format') \
900 private_format = self.request.GET.get('private_format') \
901 or SshKeyModel.DEFAULT_PRIVATE_KEY_FORMAT
901 or SshKeyModel.DEFAULT_PRIVATE_KEY_FORMAT
902 c.private, c.public = SshKeyModel().generate_keypair(
902 c.private, c.public = SshKeyModel().generate_keypair(
903 comment=comment, private_format=private_format)
903 comment=comment, private_format=private_format)
904
904
905 return self._get_template_context(c)
905 return self._get_template_context(c)
906
906
907 @LoginRequired()
907 @LoginRequired()
908 @HasPermissionAllDecorator('hg.admin')
908 @HasPermissionAllDecorator('hg.admin')
909 @CSRFRequired()
909 @CSRFRequired()
910 def ssh_keys_add(self):
910 def ssh_keys_add(self):
911 _ = self.request.translate
911 _ = self.request.translate
912 c = self.load_default_context()
912 c = self.load_default_context()
913
913
914 user_id = self.db_user_id
914 user_id = self.db_user_id
915 c.user = self.db_user
915 c.user = self.db_user
916
916
917 user_data = c.user.get_api_data()
917 user_data = c.user.get_api_data()
918 key_data = self.request.POST.get('key_data')
918 key_data = self.request.POST.get('key_data')
919 description = self.request.POST.get('description')
919 description = self.request.POST.get('description')
920
920
921 fingerprint = 'unknown'
921 fingerprint = 'unknown'
922 try:
922 try:
923 if not key_data:
923 if not key_data:
924 raise ValueError('Please add a valid public key')
924 raise ValueError('Please add a valid public key')
925
925
926 key = SshKeyModel().parse_key(key_data.strip())
926 key = SshKeyModel().parse_key(key_data.strip())
927 fingerprint = key.hash_md5()
927 fingerprint = key.hash_md5()
928
928
929 ssh_key = SshKeyModel().create(
929 ssh_key = SshKeyModel().create(
930 c.user.user_id, fingerprint, key.keydata, description)
930 c.user.user_id, fingerprint, key.keydata, description)
931 ssh_key_data = ssh_key.get_api_data()
931 ssh_key_data = ssh_key.get_api_data()
932
932
933 audit_logger.store_web(
933 audit_logger.store_web(
934 'user.edit.ssh_key.add', action_data={
934 'user.edit.ssh_key.add', action_data={
935 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
935 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
936 user=self._rhodecode_user, )
936 user=self._rhodecode_user, )
937 Session().commit()
937 Session().commit()
938
938
939 # Trigger an event on change of keys.
939 # Trigger an event on change of keys.
940 trigger(SshKeyFileChangeEvent(), self.request.registry)
940 trigger(SshKeyFileChangeEvent(), self.request.registry)
941
941
942 h.flash(_("Ssh Key successfully created"), category='success')
942 h.flash(_("Ssh Key successfully created"), category='success')
943
943
944 except IntegrityError:
944 except IntegrityError:
945 log.exception("Exception during ssh key saving")
945 log.exception("Exception during ssh key saving")
946 err = 'Such key with fingerprint `{}` already exists, ' \
946 err = 'Such key with fingerprint `{}` already exists, ' \
947 'please use a different one'.format(fingerprint)
947 'please use a different one'.format(fingerprint)
948 h.flash(_('An error occurred during ssh key saving: {}').format(err),
948 h.flash(_('An error occurred during ssh key saving: {}').format(err),
949 category='error')
949 category='error')
950 except Exception as e:
950 except Exception as e:
951 log.exception("Exception during ssh key saving")
951 log.exception("Exception during ssh key saving")
952 h.flash(_('An error occurred during ssh key saving: {}').format(e),
952 h.flash(_('An error occurred during ssh key saving: {}').format(e),
953 category='error')
953 category='error')
954
954
955 return HTTPFound(
955 return HTTPFound(
956 h.route_path('edit_user_ssh_keys', user_id=user_id))
956 h.route_path('edit_user_ssh_keys', user_id=user_id))
957
957
958 @LoginRequired()
958 @LoginRequired()
959 @HasPermissionAllDecorator('hg.admin')
959 @HasPermissionAllDecorator('hg.admin')
960 @CSRFRequired()
960 @CSRFRequired()
961 def ssh_keys_delete(self):
961 def ssh_keys_delete(self):
962 _ = self.request.translate
962 _ = self.request.translate
963 c = self.load_default_context()
963 c = self.load_default_context()
964
964
965 user_id = self.db_user_id
965 user_id = self.db_user_id
966 c.user = self.db_user
966 c.user = self.db_user
967
967
968 user_data = c.user.get_api_data()
968 user_data = c.user.get_api_data()
969
969
970 del_ssh_key = self.request.POST.get('del_ssh_key')
970 del_ssh_key = self.request.POST.get('del_ssh_key')
971
971
972 if del_ssh_key:
972 if del_ssh_key:
973 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
973 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
974 ssh_key_data = ssh_key.get_api_data()
974 ssh_key_data = ssh_key.get_api_data()
975
975
976 SshKeyModel().delete(del_ssh_key, c.user.user_id)
976 SshKeyModel().delete(del_ssh_key, c.user.user_id)
977 audit_logger.store_web(
977 audit_logger.store_web(
978 'user.edit.ssh_key.delete', action_data={
978 'user.edit.ssh_key.delete', action_data={
979 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
979 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
980 user=self._rhodecode_user,)
980 user=self._rhodecode_user,)
981 Session().commit()
981 Session().commit()
982 # Trigger an event on change of keys.
982 # Trigger an event on change of keys.
983 trigger(SshKeyFileChangeEvent(), self.request.registry)
983 trigger(SshKeyFileChangeEvent(), self.request.registry)
984 h.flash(_("Ssh key successfully deleted"), category='success')
984 h.flash(_("Ssh key successfully deleted"), category='success')
985
985
986 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
986 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
987
987
988 @LoginRequired()
988 @LoginRequired()
989 @HasPermissionAllDecorator('hg.admin')
989 @HasPermissionAllDecorator('hg.admin')
990 def emails(self):
990 def emails(self):
991 _ = self.request.translate
991 _ = self.request.translate
992 c = self.load_default_context()
992 c = self.load_default_context()
993 c.user = self.db_user
993 c.user = self.db_user
994
994
995 c.active = 'emails'
995 c.active = 'emails'
996 c.user_email_map = UserEmailMap.query() \
996 c.user_email_map = UserEmailMap.query() \
997 .filter(UserEmailMap.user == c.user).all()
997 .filter(UserEmailMap.user == c.user).all()
998
998
999 return self._get_template_context(c)
999 return self._get_template_context(c)
1000
1000
1001 @LoginRequired()
1001 @LoginRequired()
1002 @HasPermissionAllDecorator('hg.admin')
1002 @HasPermissionAllDecorator('hg.admin')
1003 @CSRFRequired()
1003 @CSRFRequired()
1004 def emails_add(self):
1004 def emails_add(self):
1005 _ = self.request.translate
1005 _ = self.request.translate
1006 c = self.load_default_context()
1006 c = self.load_default_context()
1007
1007
1008 user_id = self.db_user_id
1008 user_id = self.db_user_id
1009 c.user = self.db_user
1009 c.user = self.db_user
1010
1010
1011 email = self.request.POST.get('new_email')
1011 email = self.request.POST.get('new_email')
1012 user_data = c.user.get_api_data()
1012 user_data = c.user.get_api_data()
1013 try:
1013 try:
1014
1014
1015 form = UserExtraEmailForm(self.request.translate)()
1015 form = UserExtraEmailForm(self.request.translate)()
1016 data = form.to_python({'email': email})
1016 data = form.to_python({'email': email})
1017 email = data['email']
1017 email = data['email']
1018
1018
1019 UserModel().add_extra_email(c.user.user_id, email)
1019 UserModel().add_extra_email(c.user.user_id, email)
1020 audit_logger.store_web(
1020 audit_logger.store_web(
1021 'user.edit.email.add',
1021 'user.edit.email.add',
1022 action_data={'email': email, 'user': user_data},
1022 action_data={'email': email, 'user': user_data},
1023 user=self._rhodecode_user)
1023 user=self._rhodecode_user)
1024 Session().commit()
1024 Session().commit()
1025 h.flash(_("Added new email address `%s` for user account") % email,
1025 h.flash(_("Added new email address `%s` for user account") % email,
1026 category='success')
1026 category='success')
1027 except formencode.Invalid as error:
1027 except formencode.Invalid as error:
1028 msg = error.unpack_errors()['email']
1028 msg = error.unpack_errors()['email']
1029 h.flash(h.escape(msg), category='error')
1029 h.flash(h.escape(msg), category='error')
1030 except IntegrityError:
1030 except IntegrityError:
1031 log.warning("Email %s already exists", email)
1031 log.warning("Email %s already exists", email)
1032 h.flash(_('Email `{}` is already registered for another user.').format(email),
1032 h.flash(_('Email `{}` is already registered for another user.').format(email),
1033 category='error')
1033 category='error')
1034 except Exception:
1034 except Exception:
1035 log.exception("Exception during email saving")
1035 log.exception("Exception during email saving")
1036 h.flash(_('An error occurred during email saving'),
1036 h.flash(_('An error occurred during email saving'),
1037 category='error')
1037 category='error')
1038 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1038 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1039
1039
1040 @LoginRequired()
1040 @LoginRequired()
1041 @HasPermissionAllDecorator('hg.admin')
1041 @HasPermissionAllDecorator('hg.admin')
1042 @CSRFRequired()
1042 @CSRFRequired()
1043 def emails_delete(self):
1043 def emails_delete(self):
1044 _ = self.request.translate
1044 _ = self.request.translate
1045 c = self.load_default_context()
1045 c = self.load_default_context()
1046
1046
1047 user_id = self.db_user_id
1047 user_id = self.db_user_id
1048 c.user = self.db_user
1048 c.user = self.db_user
1049
1049
1050 email_id = self.request.POST.get('del_email_id')
1050 email_id = self.request.POST.get('del_email_id')
1051 user_model = UserModel()
1051 user_model = UserModel()
1052
1052
1053 email = UserEmailMap.query().get(email_id).email
1053 email = UserEmailMap.query().get(email_id).email
1054 user_data = c.user.get_api_data()
1054 user_data = c.user.get_api_data()
1055 user_model.delete_extra_email(c.user.user_id, email_id)
1055 user_model.delete_extra_email(c.user.user_id, email_id)
1056 audit_logger.store_web(
1056 audit_logger.store_web(
1057 'user.edit.email.delete',
1057 'user.edit.email.delete',
1058 action_data={'email': email, 'user': user_data},
1058 action_data={'email': email, 'user': user_data},
1059 user=self._rhodecode_user)
1059 user=self._rhodecode_user)
1060 Session().commit()
1060 Session().commit()
1061 h.flash(_("Removed email address from user account"),
1061 h.flash(_("Removed email address from user account"),
1062 category='success')
1062 category='success')
1063 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1063 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1064
1064
1065 @LoginRequired()
1065 @LoginRequired()
1066 @HasPermissionAllDecorator('hg.admin')
1066 @HasPermissionAllDecorator('hg.admin')
1067 def ips(self):
1067 def ips(self):
1068 _ = self.request.translate
1068 _ = self.request.translate
1069 c = self.load_default_context()
1069 c = self.load_default_context()
1070 c.user = self.db_user
1070 c.user = self.db_user
1071
1071
1072 c.active = 'ips'
1072 c.active = 'ips'
1073 c.user_ip_map = UserIpMap.query() \
1073 c.user_ip_map = UserIpMap.query() \
1074 .filter(UserIpMap.user == c.user).all()
1074 .filter(UserIpMap.user == c.user).all()
1075
1075
1076 c.inherit_default_ips = c.user.inherit_default_permissions
1076 c.inherit_default_ips = c.user.inherit_default_permissions
1077 c.default_user_ip_map = UserIpMap.query() \
1077 c.default_user_ip_map = UserIpMap.query() \
1078 .filter(UserIpMap.user == User.get_default_user()).all()
1078 .filter(UserIpMap.user == User.get_default_user()).all()
1079
1079
1080 return self._get_template_context(c)
1080 return self._get_template_context(c)
1081
1081
1082 @LoginRequired()
1082 @LoginRequired()
1083 @HasPermissionAllDecorator('hg.admin')
1083 @HasPermissionAllDecorator('hg.admin')
1084 @CSRFRequired()
1084 @CSRFRequired()
1085 # NOTE(marcink): this view is allowed for default users, as we can
1085 # NOTE(marcink): this view is allowed for default users, as we can
1086 # edit their IP white list
1086 # edit their IP white list
1087 def ips_add(self):
1087 def ips_add(self):
1088 _ = self.request.translate
1088 _ = self.request.translate
1089 c = self.load_default_context()
1089 c = self.load_default_context()
1090
1090
1091 user_id = self.db_user_id
1091 user_id = self.db_user_id
1092 c.user = self.db_user
1092 c.user = self.db_user
1093
1093
1094 user_model = UserModel()
1094 user_model = UserModel()
1095 desc = self.request.POST.get('description')
1095 desc = self.request.POST.get('description')
1096 try:
1096 try:
1097 ip_list = user_model.parse_ip_range(
1097 ip_list = user_model.parse_ip_range(
1098 self.request.POST.get('new_ip'))
1098 self.request.POST.get('new_ip'))
1099 except Exception as e:
1099 except Exception as e:
1100 ip_list = []
1100 ip_list = []
1101 log.exception("Exception during ip saving")
1101 log.exception("Exception during ip saving")
1102 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1102 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1103 category='error')
1103 category='error')
1104 added = []
1104 added = []
1105 user_data = c.user.get_api_data()
1105 user_data = c.user.get_api_data()
1106 for ip in ip_list:
1106 for ip in ip_list:
1107 try:
1107 try:
1108 form = UserExtraIpForm(self.request.translate)()
1108 form = UserExtraIpForm(self.request.translate)()
1109 data = form.to_python({'ip': ip})
1109 data = form.to_python({'ip': ip})
1110 ip = data['ip']
1110 ip = data['ip']
1111
1111
1112 user_model.add_extra_ip(c.user.user_id, ip, desc)
1112 user_model.add_extra_ip(c.user.user_id, ip, desc)
1113 audit_logger.store_web(
1113 audit_logger.store_web(
1114 'user.edit.ip.add',
1114 'user.edit.ip.add',
1115 action_data={'ip': ip, 'user': user_data},
1115 action_data={'ip': ip, 'user': user_data},
1116 user=self._rhodecode_user)
1116 user=self._rhodecode_user)
1117 Session().commit()
1117 Session().commit()
1118 added.append(ip)
1118 added.append(ip)
1119 except formencode.Invalid as error:
1119 except formencode.Invalid as error:
1120 msg = error.unpack_errors()['ip']
1120 msg = error.unpack_errors()['ip']
1121 h.flash(msg, category='error')
1121 h.flash(msg, category='error')
1122 except Exception:
1122 except Exception:
1123 log.exception("Exception during ip saving")
1123 log.exception("Exception during ip saving")
1124 h.flash(_('An error occurred during ip saving'),
1124 h.flash(_('An error occurred during ip saving'),
1125 category='error')
1125 category='error')
1126 if added:
1126 if added:
1127 h.flash(
1127 h.flash(
1128 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1128 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1129 category='success')
1129 category='success')
1130 if 'default_user' in self.request.POST:
1130 if 'default_user' in self.request.POST:
1131 # case for editing global IP list we do it for 'DEFAULT' user
1131 # case for editing global IP list we do it for 'DEFAULT' user
1132 raise HTTPFound(h.route_path('admin_permissions_ips'))
1132 raise HTTPFound(h.route_path('admin_permissions_ips'))
1133 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1133 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1134
1134
1135 @LoginRequired()
1135 @LoginRequired()
1136 @HasPermissionAllDecorator('hg.admin')
1136 @HasPermissionAllDecorator('hg.admin')
1137 @CSRFRequired()
1137 @CSRFRequired()
1138 # NOTE(marcink): this view is allowed for default users, as we can
1138 # NOTE(marcink): this view is allowed for default users, as we can
1139 # edit their IP white list
1139 # edit their IP white list
1140 def ips_delete(self):
1140 def ips_delete(self):
1141 _ = self.request.translate
1141 _ = self.request.translate
1142 c = self.load_default_context()
1142 c = self.load_default_context()
1143
1143
1144 user_id = self.db_user_id
1144 user_id = self.db_user_id
1145 c.user = self.db_user
1145 c.user = self.db_user
1146
1146
1147 ip_id = self.request.POST.get('del_ip_id')
1147 ip_id = self.request.POST.get('del_ip_id')
1148 user_model = UserModel()
1148 user_model = UserModel()
1149 user_data = c.user.get_api_data()
1149 user_data = c.user.get_api_data()
1150 ip = UserIpMap.query().get(ip_id).ip_addr
1150 ip = UserIpMap.query().get(ip_id).ip_addr
1151 user_model.delete_extra_ip(c.user.user_id, ip_id)
1151 user_model.delete_extra_ip(c.user.user_id, ip_id)
1152 audit_logger.store_web(
1152 audit_logger.store_web(
1153 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1153 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1154 user=self._rhodecode_user)
1154 user=self._rhodecode_user)
1155 Session().commit()
1155 Session().commit()
1156 h.flash(_("Removed ip address from user whitelist"), category='success')
1156 h.flash(_("Removed ip address from user whitelist"), category='success')
1157
1157
1158 if 'default_user' in self.request.POST:
1158 if 'default_user' in self.request.POST:
1159 # case for editing global IP list we do it for 'DEFAULT' user
1159 # case for editing global IP list we do it for 'DEFAULT' user
1160 raise HTTPFound(h.route_path('admin_permissions_ips'))
1160 raise HTTPFound(h.route_path('admin_permissions_ips'))
1161 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1161 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1162
1162
1163 @LoginRequired()
1163 @LoginRequired()
1164 @HasPermissionAllDecorator('hg.admin')
1164 @HasPermissionAllDecorator('hg.admin')
1165 def groups_management(self):
1165 def groups_management(self):
1166 c = self.load_default_context()
1166 c = self.load_default_context()
1167 c.user = self.db_user
1167 c.user = self.db_user
1168 c.data = c.user.group_member
1168 c.data = c.user.group_member
1169
1169
1170 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1170 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1171 for group in c.user.group_member]
1171 for group in c.user.group_member]
1172 c.groups = ext_json.str_json(groups)
1172 c.groups = ext_json.str_json(groups)
1173 c.active = 'groups'
1173 c.active = 'groups'
1174
1174
1175 return self._get_template_context(c)
1175 return self._get_template_context(c)
1176
1176
1177 @LoginRequired()
1177 @LoginRequired()
1178 @HasPermissionAllDecorator('hg.admin')
1178 @HasPermissionAllDecorator('hg.admin')
1179 @CSRFRequired()
1179 @CSRFRequired()
1180 def groups_management_updates(self):
1180 def groups_management_updates(self):
1181 _ = self.request.translate
1181 _ = self.request.translate
1182 c = self.load_default_context()
1182 c = self.load_default_context()
1183
1183
1184 user_id = self.db_user_id
1184 user_id = self.db_user_id
1185 c.user = self.db_user
1185 c.user = self.db_user
1186
1186
1187 user_groups = set(self.request.POST.getall('users_group_id'))
1187 user_groups = set(self.request.POST.getall('users_group_id'))
1188 user_groups_objects = []
1188 user_groups_objects = []
1189
1189
1190 for ugid in user_groups:
1190 for ugid in user_groups:
1191 user_groups_objects.append(
1191 user_groups_objects.append(
1192 UserGroupModel().get_group(safe_int(ugid)))
1192 UserGroupModel().get_group(safe_int(ugid)))
1193 user_group_model = UserGroupModel()
1193 user_group_model = UserGroupModel()
1194 added_to_groups, removed_from_groups = \
1194 added_to_groups, removed_from_groups = \
1195 user_group_model.change_groups(c.user, user_groups_objects)
1195 user_group_model.change_groups(c.user, user_groups_objects)
1196
1196
1197 user_data = c.user.get_api_data()
1197 user_data = c.user.get_api_data()
1198 for user_group_id in added_to_groups:
1198 for user_group_id in added_to_groups:
1199 user_group = UserGroup.get(user_group_id)
1199 user_group = UserGroup.get(user_group_id)
1200 old_values = user_group.get_api_data()
1200 old_values = user_group.get_api_data()
1201 audit_logger.store_web(
1201 audit_logger.store_web(
1202 'user_group.edit.member.add',
1202 'user_group.edit.member.add',
1203 action_data={'user': user_data, 'old_data': old_values},
1203 action_data={'user': user_data, 'old_data': old_values},
1204 user=self._rhodecode_user)
1204 user=self._rhodecode_user)
1205
1205
1206 for user_group_id in removed_from_groups:
1206 for user_group_id in removed_from_groups:
1207 user_group = UserGroup.get(user_group_id)
1207 user_group = UserGroup.get(user_group_id)
1208 old_values = user_group.get_api_data()
1208 old_values = user_group.get_api_data()
1209 audit_logger.store_web(
1209 audit_logger.store_web(
1210 'user_group.edit.member.delete',
1210 'user_group.edit.member.delete',
1211 action_data={'user': user_data, 'old_data': old_values},
1211 action_data={'user': user_data, 'old_data': old_values},
1212 user=self._rhodecode_user)
1212 user=self._rhodecode_user)
1213
1213
1214 Session().commit()
1214 Session().commit()
1215 c.active = 'user_groups_management'
1215 c.active = 'user_groups_management'
1216 h.flash(_("Groups successfully changed"), category='success')
1216 h.flash(_("Groups successfully changed"), category='success')
1217
1217
1218 return HTTPFound(h.route_path(
1218 return HTTPFound(h.route_path(
1219 'edit_user_groups_management', user_id=user_id))
1219 'edit_user_groups_management', user_id=user_id))
1220
1220
1221 @LoginRequired()
1221 @LoginRequired()
1222 @HasPermissionAllDecorator('hg.admin')
1222 @HasPermissionAllDecorator('hg.admin')
1223 def user_audit_logs(self):
1223 def user_audit_logs(self):
1224 _ = self.request.translate
1224 _ = self.request.translate
1225 c = self.load_default_context()
1225 c = self.load_default_context()
1226 c.user = self.db_user
1226 c.user = self.db_user
1227
1227
1228 c.active = 'audit'
1228 c.active = 'audit'
1229
1229
1230 p = safe_int(self.request.GET.get('page', 1), 1)
1230 p = safe_int(self.request.GET.get('page', 1), 1)
1231
1231
1232 filter_term = self.request.GET.get('filter')
1232 filter_term = self.request.GET.get('filter')
1233 user_log = UserModel().get_user_log(c.user, filter_term)
1233 user_log = UserModel().get_user_log(c.user, filter_term)
1234
1234
1235 def url_generator(page_num):
1235 def url_generator(page_num):
1236 query_params = {
1236 query_params = {
1237 'page': page_num
1237 'page': page_num
1238 }
1238 }
1239 if filter_term:
1239 if filter_term:
1240 query_params['filter'] = filter_term
1240 query_params['filter'] = filter_term
1241 return self.request.current_route_path(_query=query_params)
1241 return self.request.current_route_path(_query=query_params)
1242
1242
1243 c.audit_logs = SqlPage(
1243 c.audit_logs = SqlPage(
1244 user_log, page=p, items_per_page=10, url_maker=url_generator)
1244 user_log, page=p, items_per_page=10, url_maker=url_generator)
1245 c.filter_term = filter_term
1245 c.filter_term = filter_term
1246 return self._get_template_context(c)
1246 return self._get_template_context(c)
1247
1247
1248 @LoginRequired()
1248 @LoginRequired()
1249 @HasPermissionAllDecorator('hg.admin')
1249 @HasPermissionAllDecorator('hg.admin')
1250 def user_audit_logs_download(self):
1250 def user_audit_logs_download(self):
1251 _ = self.request.translate
1251 _ = self.request.translate
1252 c = self.load_default_context()
1252 c = self.load_default_context()
1253 c.user = self.db_user
1253 c.user = self.db_user
1254
1254
1255 user_log = UserModel().get_user_log(c.user, filter_term=None)
1255 user_log = UserModel().get_user_log(c.user, filter_term=None)
1256
1256
1257 audit_log_data = {}
1257 audit_log_data = {}
1258 for entry in user_log:
1258 for entry in user_log:
1259 audit_log_data[entry.user_log_id] = entry.get_dict()
1259 audit_log_data[entry.user_log_id] = entry.get_dict()
1260
1260
1261 response = Response(ext_json.formatted_str_json(audit_log_data))
1261 response = Response(ext_json.formatted_str_json(audit_log_data))
1262 response.content_disposition = f'attachment; filename=user_{c.user.user_id}_audit_logs.json'
1262 response.content_disposition = f'attachment; filename=user_{c.user.user_id}_audit_logs.json'
1263 response.content_type = 'application/json'
1263 response.content_type = 'application/json'
1264
1264
1265 return response
1265 return response
1266
1266
1267 @LoginRequired()
1267 @LoginRequired()
1268 @HasPermissionAllDecorator('hg.admin')
1268 @HasPermissionAllDecorator('hg.admin')
1269 def user_perms_summary(self):
1269 def user_perms_summary(self):
1270 _ = self.request.translate
1270 _ = self.request.translate
1271 c = self.load_default_context()
1271 c = self.load_default_context()
1272 c.user = self.db_user
1272 c.user = self.db_user
1273
1273
1274 c.active = 'perms_summary'
1274 c.active = 'perms_summary'
1275 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1275 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1276
1276
1277 return self._get_template_context(c)
1277 return self._get_template_context(c)
1278
1278
1279 @LoginRequired()
1279 @LoginRequired()
1280 @HasPermissionAllDecorator('hg.admin')
1280 @HasPermissionAllDecorator('hg.admin')
1281 def user_perms_summary_json(self):
1281 def user_perms_summary_json(self):
1282 self.load_default_context()
1282 self.load_default_context()
1283 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1283 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1284
1284
1285 return perm_user.permissions
1285 return perm_user.permissions
1286
1286
1287 @LoginRequired()
1287 @LoginRequired()
1288 @HasPermissionAllDecorator('hg.admin')
1288 @HasPermissionAllDecorator('hg.admin')
1289 def user_caches(self):
1289 def user_caches(self):
1290 _ = self.request.translate
1290 _ = self.request.translate
1291 c = self.load_default_context()
1291 c = self.load_default_context()
1292 c.user = self.db_user
1292 c.user = self.db_user
1293
1293
1294 c.active = 'caches'
1294 c.active = 'caches'
1295 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1295 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1296
1296
1297 cache_namespace_uid = f'cache_user_auth.{rc_cache.PERMISSIONS_CACHE_VER}.{self.db_user.user_id}'
1297 cache_namespace_uid = f'cache_user_auth.{rc_cache.PERMISSIONS_CACHE_VER}.{self.db_user.user_id}'
1298 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1298 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1299 c.backend = c.region.backend
1299 c.backend = c.region.backend
1300 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1300 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1301
1301
1302 return self._get_template_context(c)
1302 return self._get_template_context(c)
1303
1303
1304 @LoginRequired()
1304 @LoginRequired()
1305 @HasPermissionAllDecorator('hg.admin')
1305 @HasPermissionAllDecorator('hg.admin')
1306 @CSRFRequired()
1306 @CSRFRequired()
1307 def user_caches_update(self):
1307 def user_caches_update(self):
1308 _ = self.request.translate
1308 _ = self.request.translate
1309 c = self.load_default_context()
1309 c = self.load_default_context()
1310 c.user = self.db_user
1310 c.user = self.db_user
1311
1311
1312 c.active = 'caches'
1312 c.active = 'caches'
1313 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1313 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1314
1314
1315 cache_namespace_uid = f'cache_user_auth.{rc_cache.PERMISSIONS_CACHE_VER}.{self.db_user.user_id}'
1315 cache_namespace_uid = f'cache_user_auth.{rc_cache.PERMISSIONS_CACHE_VER}.{self.db_user.user_id}'
1316 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid, method=rc_cache.CLEAR_DELETE)
1316 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid, method=rc_cache.CLEAR_DELETE)
1317
1317
1318 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1318 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1319
1319
1320 return HTTPFound(h.route_path(
1320 return HTTPFound(h.route_path(
1321 'edit_user_caches', user_id=c.user.user_id))
1321 'edit_user_caches', user_id=c.user.user_id))
@@ -1,77 +1,101 b''
1 # Copyright (C) 2016-2023 RhodeCode GmbH
1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19
19
20 from rhodecode.apps._base import ADMIN_PREFIX
20 from rhodecode.apps._base import ADMIN_PREFIX
21
21
22
22
23 def includeme(config):
23 def includeme(config):
24 from rhodecode.apps.login.views import LoginView
24 from rhodecode.apps.login.views import LoginView
25
25
26 config.add_route(
26 config.add_route(
27 name='login',
27 name='login',
28 pattern=ADMIN_PREFIX + '/login')
28 pattern=ADMIN_PREFIX + '/login')
29 config.add_view(
29 config.add_view(
30 LoginView,
30 LoginView,
31 attr='login',
31 attr='login',
32 route_name='login', request_method='GET',
32 route_name='login', request_method='GET',
33 renderer='rhodecode:templates/login.mako')
33 renderer='rhodecode:templates/login.mako')
34 config.add_view(
34 config.add_view(
35 LoginView,
35 LoginView,
36 attr='login_post',
36 attr='login_post',
37 route_name='login', request_method='POST',
37 route_name='login', request_method='POST',
38 renderer='rhodecode:templates/login.mako')
38 renderer='rhodecode:templates/login.mako')
39
39
40 config.add_route(
40 config.add_route(
41 name='logout',
41 name='logout',
42 pattern=ADMIN_PREFIX + '/logout')
42 pattern=ADMIN_PREFIX + '/logout')
43 config.add_view(
43 config.add_view(
44 LoginView,
44 LoginView,
45 attr='logout',
45 attr='logout',
46 route_name='logout', request_method='POST')
46 route_name='logout', request_method='POST')
47
47
48 config.add_route(
48 config.add_route(
49 name='register',
49 name='register',
50 pattern=ADMIN_PREFIX + '/register')
50 pattern=ADMIN_PREFIX + '/register')
51 config.add_view(
51 config.add_view(
52 LoginView,
52 LoginView,
53 attr='register',
53 attr='register',
54 route_name='register', request_method='GET',
54 route_name='register', request_method='GET',
55 renderer='rhodecode:templates/register.mako')
55 renderer='rhodecode:templates/register.mako')
56 config.add_view(
56 config.add_view(
57 LoginView,
57 LoginView,
58 attr='register_post',
58 attr='register_post',
59 route_name='register', request_method='POST',
59 route_name='register', request_method='POST',
60 renderer='rhodecode:templates/register.mako')
60 renderer='rhodecode:templates/register.mako')
61
61
62 config.add_route(
62 config.add_route(
63 name='reset_password',
63 name='reset_password',
64 pattern=ADMIN_PREFIX + '/password_reset')
64 pattern=ADMIN_PREFIX + '/password_reset')
65 config.add_view(
65 config.add_view(
66 LoginView,
66 LoginView,
67 attr='password_reset',
67 attr='password_reset',
68 route_name='reset_password', request_method=('GET', 'POST'),
68 route_name='reset_password', request_method=('GET', 'POST'),
69 renderer='rhodecode:templates/password_reset.mako')
69 renderer='rhodecode:templates/password_reset.mako')
70
70
71 config.add_route(
71 config.add_route(
72 name='reset_password_confirmation',
72 name='reset_password_confirmation',
73 pattern=ADMIN_PREFIX + '/password_reset_confirmation')
73 pattern=ADMIN_PREFIX + '/password_reset_confirmation')
74 config.add_view(
74 config.add_view(
75 LoginView,
75 LoginView,
76 attr='password_reset_confirmation',
76 attr='password_reset_confirmation',
77 route_name='reset_password_confirmation', request_method='GET')
77 route_name='reset_password_confirmation', request_method='GET')
78
79 config.add_route(
80 name='setup_2fa',
81 pattern=ADMIN_PREFIX + '/setup_2fa')
82 config.add_view(
83 LoginView,
84 attr='setup_2fa',
85 route_name='setup_2fa', request_method=['GET', 'POST'],
86 renderer='rhodecode:templates/configure_2fa.mako')
87
88 config.add_route(
89 name='check_2fa',
90 pattern=ADMIN_PREFIX + '/check_2fa')
91 config.add_view(
92 LoginView,
93 attr='verify_2fa',
94 route_name='check_2fa', request_method='GET',
95 renderer='rhodecode:templates/verify_2fa.mako')
96 config.add_view(
97 LoginView,
98 attr='verify_2fa',
99 route_name='check_2fa', request_method='POST',
100 renderer='rhodecode:templates/verify_2fa.mako')
101
@@ -1,581 +1,593 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import urllib.parse
19 import urllib.parse
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24
24
25 from rhodecode.lib.auth import check_password
25 from rhodecode.lib.auth import check_password
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
27 from rhodecode.model.auth_token import AuthTokenModel
27 from rhodecode.model.auth_token import AuthTokenModel
28 from rhodecode.model.db import User, Notification, UserApiKeys
28 from rhodecode.model.db import User, Notification, UserApiKeys
29 from rhodecode.model.meta import Session
29 from rhodecode.model.meta import Session
30
30
31 from rhodecode.tests import (
31 from rhodecode.tests import (
32 assert_session_flash, HG_REPO, TEST_USER_ADMIN_LOGIN,
32 assert_session_flash, HG_REPO, TEST_USER_ADMIN_LOGIN,
33 no_newline_id_generator)
33 no_newline_id_generator)
34 from rhodecode.tests.fixture import Fixture
34 from rhodecode.tests.fixture import Fixture
35 from rhodecode.tests.routes import route_path
35 from rhodecode.tests.routes import route_path
36
36
37 fixture = Fixture()
37 fixture = Fixture()
38
38
39 whitelist_view = ['RepoCommitsView:repo_commit_raw']
39 whitelist_view = ['RepoCommitsView:repo_commit_raw']
40
40
41
41
42 @pytest.mark.usefixtures('app')
42 @pytest.mark.usefixtures('app')
43 class TestLoginController(object):
43 class TestLoginController(object):
44 destroy_users = set()
44 destroy_users = set()
45
45
46 @classmethod
46 @classmethod
47 def teardown_class(cls):
47 def teardown_class(cls):
48 fixture.destroy_users(cls.destroy_users)
48 fixture.destroy_users(cls.destroy_users)
49
49
50 def teardown_method(self, method):
50 def teardown_method(self, method):
51 for n in Notification.query().all():
51 for n in Notification.query().all():
52 Session().delete(n)
52 Session().delete(n)
53
53
54 Session().commit()
54 Session().commit()
55 assert Notification.query().all() == []
55 assert Notification.query().all() == []
56
56
57 def test_index(self):
57 def test_index(self):
58 response = self.app.get(route_path('login'))
58 response = self.app.get(route_path('login'))
59 assert response.status == '200 OK'
59 assert response.status == '200 OK'
60 # Test response...
60 # Test response...
61
61
62 def test_login_admin_ok(self):
62 def test_login_admin_ok(self):
63 response = self.app.post(route_path('login'),
63 response = self.app.post(route_path('login'),
64 {'username': 'test_admin',
64 {'username': 'test_admin',
65 'password': 'test12'}, status=302)
65 'password': 'test12'}, status=302)
66 response = response.follow()
66 response = response.follow()
67 session = response.get_session_from_response()
67 session = response.get_session_from_response()
68 username = session['rhodecode_user'].get('username')
68 username = session['rhodecode_user'].get('username')
69 assert username == 'test_admin'
69 assert username == 'test_admin'
70 response.mustcontain('logout')
70 response.mustcontain('logout')
71
71
72 def test_login_regular_ok(self):
72 def test_login_regular_ok(self):
73 response = self.app.post(route_path('login'),
73 response = self.app.post(route_path('login'),
74 {'username': 'test_regular',
74 {'username': 'test_regular',
75 'password': 'test12'}, status=302)
75 'password': 'test12'}, status=302)
76
76
77 response = response.follow()
77 response = response.follow()
78 session = response.get_session_from_response()
78 session = response.get_session_from_response()
79 username = session['rhodecode_user'].get('username')
79 username = session['rhodecode_user'].get('username')
80 assert username == 'test_regular'
80 assert username == 'test_regular'
81 response.mustcontain('logout')
81 response.mustcontain('logout')
82
82
83 def test_login_with_primary_email(self):
84 user_email = 'test_regular@mail.com'
85 response = self.app.post(route_path('login'),
86 {'username': user_email,
87 'password': 'test12'}, status=302)
88 response = response.follow()
89 session = response.get_session_from_response()
90 user = session['rhodecode_user']
91 assert user['username'] == user_email.split('@')[0]
92 assert user['is_authenticated']
93 response.mustcontain('logout')
94
83 def test_login_regular_forbidden_when_super_admin_restriction(self):
95 def test_login_regular_forbidden_when_super_admin_restriction(self):
84 from rhodecode.authentication.plugins.auth_rhodecode import RhodeCodeAuthPlugin
96 from rhodecode.authentication.plugins.auth_rhodecode import RhodeCodeAuthPlugin
85 with fixture.auth_restriction(self.app._pyramid_registry,
97 with fixture.auth_restriction(self.app._pyramid_registry,
86 RhodeCodeAuthPlugin.AUTH_RESTRICTION_SUPER_ADMIN):
98 RhodeCodeAuthPlugin.AUTH_RESTRICTION_SUPER_ADMIN):
87 response = self.app.post(route_path('login'),
99 response = self.app.post(route_path('login'),
88 {'username': 'test_regular',
100 {'username': 'test_regular',
89 'password': 'test12'})
101 'password': 'test12'})
90
102
91 response.mustcontain('invalid user name')
103 response.mustcontain('invalid user name')
92 response.mustcontain('invalid password')
104 response.mustcontain('invalid password')
93
105
94 def test_login_regular_forbidden_when_scope_restriction(self):
106 def test_login_regular_forbidden_when_scope_restriction(self):
95 from rhodecode.authentication.plugins.auth_rhodecode import RhodeCodeAuthPlugin
107 from rhodecode.authentication.plugins.auth_rhodecode import RhodeCodeAuthPlugin
96 with fixture.scope_restriction(self.app._pyramid_registry,
108 with fixture.scope_restriction(self.app._pyramid_registry,
97 RhodeCodeAuthPlugin.AUTH_RESTRICTION_SCOPE_VCS):
109 RhodeCodeAuthPlugin.AUTH_RESTRICTION_SCOPE_VCS):
98 response = self.app.post(route_path('login'),
110 response = self.app.post(route_path('login'),
99 {'username': 'test_regular',
111 {'username': 'test_regular',
100 'password': 'test12'})
112 'password': 'test12'})
101
113
102 response.mustcontain('invalid user name')
114 response.mustcontain('invalid user name')
103 response.mustcontain('invalid password')
115 response.mustcontain('invalid password')
104
116
105 def test_login_ok_came_from(self):
117 def test_login_ok_came_from(self):
106 test_came_from = '/_admin/users?branch=stable'
118 test_came_from = '/_admin/users?branch=stable'
107 _url = '{}?came_from={}'.format(route_path('login'), test_came_from)
119 _url = '{}?came_from={}'.format(route_path('login'), test_came_from)
108 response = self.app.post(
120 response = self.app.post(
109 _url, {'username': 'test_admin', 'password': 'test12'}, status=302)
121 _url, {'username': 'test_admin', 'password': 'test12'}, status=302)
110
122
111 assert 'branch=stable' in response.location
123 assert 'branch=stable' in response.location
112 response = response.follow()
124 response = response.follow()
113
125
114 assert response.status == '200 OK'
126 assert response.status == '200 OK'
115 response.mustcontain('Users administration')
127 response.mustcontain('Users administration')
116
128
117 def test_redirect_to_login_with_get_args(self):
129 def test_redirect_to_login_with_get_args(self):
118 with fixture.anon_access(False):
130 with fixture.anon_access(False):
119 kwargs = {'branch': 'stable'}
131 kwargs = {'branch': 'stable'}
120 response = self.app.get(
132 response = self.app.get(
121 h.route_path('repo_summary', repo_name=HG_REPO, _query=kwargs),
133 h.route_path('repo_summary', repo_name=HG_REPO, _query=kwargs),
122 status=302)
134 status=302)
123
135
124 response_query = urllib.parse.parse_qsl(response.location)
136 response_query = urllib.parse.parse_qsl(response.location)
125 assert 'branch=stable' in response_query[0][1]
137 assert 'branch=stable' in response_query[0][1]
126
138
127 def test_login_form_with_get_args(self):
139 def test_login_form_with_get_args(self):
128 _url = '{}?came_from=/_admin/users,branch=stable'.format(route_path('login'))
140 _url = '{}?came_from=/_admin/users,branch=stable'.format(route_path('login'))
129 response = self.app.get(_url)
141 response = self.app.get(_url)
130 assert 'branch%3Dstable' in response.form.action
142 assert 'branch%3Dstable' in response.form.action
131
143
132 @pytest.mark.parametrize("url_came_from", [
144 @pytest.mark.parametrize("url_came_from", [
133 'data:text/html,<script>window.alert("xss")</script>',
145 'data:text/html,<script>window.alert("xss")</script>',
134 'mailto:test@rhodecode.org',
146 'mailto:test@rhodecode.org',
135 'file:///etc/passwd',
147 'file:///etc/passwd',
136 'ftp://some.ftp.server',
148 'ftp://some.ftp.server',
137 'http://other.domain',
149 'http://other.domain',
138 ], ids=no_newline_id_generator)
150 ], ids=no_newline_id_generator)
139 def test_login_bad_came_froms(self, url_came_from):
151 def test_login_bad_came_froms(self, url_came_from):
140 _url = '{}?came_from={}'.format(route_path('login'), url_came_from)
152 _url = '{}?came_from={}'.format(route_path('login'), url_came_from)
141 response = self.app.post(
153 response = self.app.post(
142 _url, {'username': 'test_admin', 'password': 'test12'}, status=302)
154 _url, {'username': 'test_admin', 'password': 'test12'}, status=302)
143 assert response.status == '302 Found'
155 assert response.status == '302 Found'
144 response = response.follow()
156 response = response.follow()
145 assert response.status == '200 OK'
157 assert response.status == '200 OK'
146 assert response.request.path == '/'
158 assert response.request.path == '/'
147
159
148 @pytest.mark.xfail(reason="newline params changed behaviour in python3")
160 @pytest.mark.xfail(reason="newline params changed behaviour in python3")
149 @pytest.mark.parametrize("url_came_from", [
161 @pytest.mark.parametrize("url_came_from", [
150 '/\r\nX-Forwarded-Host: \rhttp://example.org',
162 '/\r\nX-Forwarded-Host: \rhttp://example.org',
151 ], ids=no_newline_id_generator)
163 ], ids=no_newline_id_generator)
152 def test_login_bad_came_froms_404(self, url_came_from):
164 def test_login_bad_came_froms_404(self, url_came_from):
153 _url = '{}?came_from={}'.format(route_path('login'), url_came_from)
165 _url = '{}?came_from={}'.format(route_path('login'), url_came_from)
154 response = self.app.post(
166 response = self.app.post(
155 _url, {'username': 'test_admin', 'password': 'test12'}, status=302)
167 _url, {'username': 'test_admin', 'password': 'test12'}, status=302)
156
168
157 response = response.follow()
169 response = response.follow()
158 assert response.status == '404 Not Found'
170 assert response.status == '404 Not Found'
159
171
160 def test_login_short_password(self):
172 def test_login_short_password(self):
161 response = self.app.post(route_path('login'),
173 response = self.app.post(route_path('login'),
162 {'username': 'test_admin',
174 {'username': 'test_admin',
163 'password': 'as'})
175 'password': 'as'})
164 assert response.status == '200 OK'
176 assert response.status == '200 OK'
165
177
166 response.mustcontain('Enter 3 characters or more')
178 response.mustcontain('Enter 3 characters or more')
167
179
168 def test_login_wrong_non_ascii_password(self, user_regular):
180 def test_login_wrong_non_ascii_password(self, user_regular):
169 response = self.app.post(
181 response = self.app.post(
170 route_path('login'),
182 route_path('login'),
171 {'username': user_regular.username,
183 {'username': user_regular.username,
172 'password': 'invalid-non-asci\xe4'.encode('utf8')})
184 'password': 'invalid-non-asci\xe4'.encode('utf8')})
173
185
174 response.mustcontain('invalid user name')
186 response.mustcontain('invalid user name')
175 response.mustcontain('invalid password')
187 response.mustcontain('invalid password')
176
188
177 def test_login_with_non_ascii_password(self, user_util):
189 def test_login_with_non_ascii_password(self, user_util):
178 password = u'valid-non-ascii\xe4'
190 password = u'valid-non-ascii\xe4'
179 user = user_util.create_user(password=password)
191 user = user_util.create_user(password=password)
180 response = self.app.post(
192 response = self.app.post(
181 route_path('login'),
193 route_path('login'),
182 {'username': user.username,
194 {'username': user.username,
183 'password': password})
195 'password': password})
184 assert response.status_code == 302
196 assert response.status_code == 302
185
197
186 def test_login_wrong_username_password(self):
198 def test_login_wrong_username_password(self):
187 response = self.app.post(route_path('login'),
199 response = self.app.post(route_path('login'),
188 {'username': 'error',
200 {'username': 'error',
189 'password': 'test12'})
201 'password': 'test12'})
190
202
191 response.mustcontain('invalid user name')
203 response.mustcontain('invalid user name')
192 response.mustcontain('invalid password')
204 response.mustcontain('invalid password')
193
205
194 def test_login_admin_ok_password_migration(self, real_crypto_backend):
206 def test_login_admin_ok_password_migration(self, real_crypto_backend):
195 from rhodecode.lib import auth
207 from rhodecode.lib import auth
196
208
197 # create new user, with sha256 password
209 # create new user, with sha256 password
198 temp_user = 'test_admin_sha256'
210 temp_user = 'test_admin_sha256'
199 user = fixture.create_user(temp_user)
211 user = fixture.create_user(temp_user)
200 user.password = auth._RhodeCodeCryptoSha256().hash_create(
212 user.password = auth._RhodeCodeCryptoSha256().hash_create(
201 b'test123')
213 b'test123')
202 Session().add(user)
214 Session().add(user)
203 Session().commit()
215 Session().commit()
204 self.destroy_users.add(temp_user)
216 self.destroy_users.add(temp_user)
205 response = self.app.post(route_path('login'),
217 response = self.app.post(route_path('login'),
206 {'username': temp_user,
218 {'username': temp_user,
207 'password': 'test123'}, status=302)
219 'password': 'test123'}, status=302)
208
220
209 response = response.follow()
221 response = response.follow()
210 session = response.get_session_from_response()
222 session = response.get_session_from_response()
211 username = session['rhodecode_user'].get('username')
223 username = session['rhodecode_user'].get('username')
212 assert username == temp_user
224 assert username == temp_user
213 response.mustcontain('logout')
225 response.mustcontain('logout')
214
226
215 # new password should be bcrypted, after log-in and transfer
227 # new password should be bcrypted, after log-in and transfer
216 user = User.get_by_username(temp_user)
228 user = User.get_by_username(temp_user)
217 assert user.password.startswith('$')
229 assert user.password.startswith('$')
218
230
219 # REGISTRATIONS
231 # REGISTRATIONS
220 def test_register(self):
232 def test_register(self):
221 response = self.app.get(route_path('register'))
233 response = self.app.get(route_path('register'))
222 response.mustcontain('Create an Account')
234 response.mustcontain('Create an Account')
223
235
224 def test_register_err_same_username(self):
236 def test_register_err_same_username(self):
225 uname = 'test_admin'
237 uname = 'test_admin'
226 response = self.app.post(
238 response = self.app.post(
227 route_path('register'),
239 route_path('register'),
228 {
240 {
229 'username': uname,
241 'username': uname,
230 'password': 'test12',
242 'password': 'test12',
231 'password_confirmation': 'test12',
243 'password_confirmation': 'test12',
232 'email': 'goodmail@domain.com',
244 'email': 'goodmail@domain.com',
233 'firstname': 'test',
245 'firstname': 'test',
234 'lastname': 'test'
246 'lastname': 'test'
235 }
247 }
236 )
248 )
237
249
238 assertr = response.assert_response()
250 assertr = response.assert_response()
239 msg = 'Username "%(username)s" already exists'
251 msg = 'Username "%(username)s" already exists'
240 msg = msg % {'username': uname}
252 msg = msg % {'username': uname}
241 assertr.element_contains('#username+.error-message', msg)
253 assertr.element_contains('#username+.error-message', msg)
242
254
243 def test_register_err_same_email(self):
255 def test_register_err_same_email(self):
244 response = self.app.post(
256 response = self.app.post(
245 route_path('register'),
257 route_path('register'),
246 {
258 {
247 'username': 'test_admin_0',
259 'username': 'test_admin_0',
248 'password': 'test12',
260 'password': 'test12',
249 'password_confirmation': 'test12',
261 'password_confirmation': 'test12',
250 'email': 'test_admin@mail.com',
262 'email': 'test_admin@mail.com',
251 'firstname': 'test',
263 'firstname': 'test',
252 'lastname': 'test'
264 'lastname': 'test'
253 }
265 }
254 )
266 )
255
267
256 assertr = response.assert_response()
268 assertr = response.assert_response()
257 msg = u'This e-mail address is already taken'
269 msg = 'This e-mail address is already taken'
258 assertr.element_contains('#email+.error-message', msg)
270 assertr.element_contains('#email+.error-message', msg)
259
271
260 def test_register_err_same_email_case_sensitive(self):
272 def test_register_err_same_email_case_sensitive(self):
261 response = self.app.post(
273 response = self.app.post(
262 route_path('register'),
274 route_path('register'),
263 {
275 {
264 'username': 'test_admin_1',
276 'username': 'test_admin_1',
265 'password': 'test12',
277 'password': 'test12',
266 'password_confirmation': 'test12',
278 'password_confirmation': 'test12',
267 'email': 'TesT_Admin@mail.COM',
279 'email': 'TesT_Admin@mail.COM',
268 'firstname': 'test',
280 'firstname': 'test',
269 'lastname': 'test'
281 'lastname': 'test'
270 }
282 }
271 )
283 )
272 assertr = response.assert_response()
284 assertr = response.assert_response()
273 msg = u'This e-mail address is already taken'
285 msg = 'This e-mail address is already taken'
274 assertr.element_contains('#email+.error-message', msg)
286 assertr.element_contains('#email+.error-message', msg)
275
287
276 def test_register_err_wrong_data(self):
288 def test_register_err_wrong_data(self):
277 response = self.app.post(
289 response = self.app.post(
278 route_path('register'),
290 route_path('register'),
279 {
291 {
280 'username': 'xs',
292 'username': 'xs',
281 'password': 'test',
293 'password': 'test',
282 'password_confirmation': 'test',
294 'password_confirmation': 'test',
283 'email': 'goodmailm',
295 'email': 'goodmailm',
284 'firstname': 'test',
296 'firstname': 'test',
285 'lastname': 'test'
297 'lastname': 'test'
286 }
298 }
287 )
299 )
288 assert response.status == '200 OK'
300 assert response.status == '200 OK'
289 response.mustcontain('An email address must contain a single @')
301 response.mustcontain('An email address must contain a single @')
290 response.mustcontain('Enter a value 6 characters long or more')
302 response.mustcontain('Enter a value 6 characters long or more')
291
303
292 def test_register_err_username(self):
304 def test_register_err_username(self):
293 response = self.app.post(
305 response = self.app.post(
294 route_path('register'),
306 route_path('register'),
295 {
307 {
296 'username': 'error user',
308 'username': 'error user',
297 'password': 'test12',
309 'password': 'test12',
298 'password_confirmation': 'test12',
310 'password_confirmation': 'test12',
299 'email': 'goodmailm',
311 'email': 'goodmailm',
300 'firstname': 'test',
312 'firstname': 'test',
301 'lastname': 'test'
313 'lastname': 'test'
302 }
314 }
303 )
315 )
304
316
305 response.mustcontain('An email address must contain a single @')
317 response.mustcontain('An email address must contain a single @')
306 response.mustcontain(
318 response.mustcontain(
307 'Username may only contain '
319 'Username may only contain '
308 'alphanumeric characters underscores, '
320 'alphanumeric characters underscores, '
309 'periods or dashes and must begin with '
321 'periods or dashes and must begin with '
310 'alphanumeric character')
322 'alphanumeric character')
311
323
312 def test_register_err_case_sensitive(self):
324 def test_register_err_case_sensitive(self):
313 usr = 'Test_Admin'
325 usr = 'Test_Admin'
314 response = self.app.post(
326 response = self.app.post(
315 route_path('register'),
327 route_path('register'),
316 {
328 {
317 'username': usr,
329 'username': usr,
318 'password': 'test12',
330 'password': 'test12',
319 'password_confirmation': 'test12',
331 'password_confirmation': 'test12',
320 'email': 'goodmailm',
332 'email': 'goodmailm',
321 'firstname': 'test',
333 'firstname': 'test',
322 'lastname': 'test'
334 'lastname': 'test'
323 }
335 }
324 )
336 )
325
337
326 assertr = response.assert_response()
338 assertr = response.assert_response()
327 msg = u'Username "%(username)s" already exists'
339 msg = u'Username "%(username)s" already exists'
328 msg = msg % {'username': usr}
340 msg = msg % {'username': usr}
329 assertr.element_contains('#username+.error-message', msg)
341 assertr.element_contains('#username+.error-message', msg)
330
342
331 def test_register_special_chars(self):
343 def test_register_special_chars(self):
332 response = self.app.post(
344 response = self.app.post(
333 route_path('register'),
345 route_path('register'),
334 {
346 {
335 'username': 'xxxaxn',
347 'username': 'xxxaxn',
336 'password': 'ąćźżąśśśś',
348 'password': 'ąćźżąśśśś',
337 'password_confirmation': 'ąćźżąśśśś',
349 'password_confirmation': 'ąćźżąśśśś',
338 'email': 'goodmailm@test.plx',
350 'email': 'goodmailm@test.plx',
339 'firstname': 'test',
351 'firstname': 'test',
340 'lastname': 'test'
352 'lastname': 'test'
341 }
353 }
342 )
354 )
343
355
344 msg = u'Invalid characters (non-ascii) in password'
356 msg = u'Invalid characters (non-ascii) in password'
345 response.mustcontain(msg)
357 response.mustcontain(msg)
346
358
347 def test_register_password_mismatch(self):
359 def test_register_password_mismatch(self):
348 response = self.app.post(
360 response = self.app.post(
349 route_path('register'),
361 route_path('register'),
350 {
362 {
351 'username': 'xs',
363 'username': 'xs',
352 'password': '123qwe',
364 'password': '123qwe',
353 'password_confirmation': 'qwe123',
365 'password_confirmation': 'qwe123',
354 'email': 'goodmailm@test.plxa',
366 'email': 'goodmailm@test.plxa',
355 'firstname': 'test',
367 'firstname': 'test',
356 'lastname': 'test'
368 'lastname': 'test'
357 }
369 }
358 )
370 )
359 msg = u'Passwords do not match'
371 msg = u'Passwords do not match'
360 response.mustcontain(msg)
372 response.mustcontain(msg)
361
373
362 def test_register_ok(self):
374 def test_register_ok(self):
363 username = 'test_regular4'
375 username = 'test_regular4'
364 password = 'qweqwe'
376 password = 'qweqwe'
365 email = 'marcin@test.com'
377 email = 'marcin@test.com'
366 name = 'testname'
378 name = 'testname'
367 lastname = 'testlastname'
379 lastname = 'testlastname'
368
380
369 # this initializes a session
381 # this initializes a session
370 response = self.app.get(route_path('register'))
382 response = self.app.get(route_path('register'))
371 response.mustcontain('Create an Account')
383 response.mustcontain('Create an Account')
372
384
373
385
374 response = self.app.post(
386 response = self.app.post(
375 route_path('register'),
387 route_path('register'),
376 {
388 {
377 'username': username,
389 'username': username,
378 'password': password,
390 'password': password,
379 'password_confirmation': password,
391 'password_confirmation': password,
380 'email': email,
392 'email': email,
381 'firstname': name,
393 'firstname': name,
382 'lastname': lastname,
394 'lastname': lastname,
383 'admin': True
395 'admin': True
384 },
396 },
385 status=302
397 status=302
386 ) # This should be overridden
398 ) # This should be overridden
387
399
388 assert_session_flash(
400 assert_session_flash(
389 response, 'You have successfully registered with RhodeCode. You can log-in now.')
401 response, 'You have successfully registered with RhodeCode. You can log-in now.')
390
402
391 ret = Session().query(User).filter(
403 ret = Session().query(User).filter(
392 User.username == 'test_regular4').one()
404 User.username == 'test_regular4').one()
393 assert ret.username == username
405 assert ret.username == username
394 assert check_password(password, ret.password)
406 assert check_password(password, ret.password)
395 assert ret.email == email
407 assert ret.email == email
396 assert ret.name == name
408 assert ret.name == name
397 assert ret.lastname == lastname
409 assert ret.lastname == lastname
398 assert ret.auth_tokens is not None
410 assert ret.auth_tokens is not None
399 assert not ret.admin
411 assert not ret.admin
400
412
401 def test_forgot_password_wrong_mail(self):
413 def test_forgot_password_wrong_mail(self):
402 bad_email = 'marcin@wrongmail.org'
414 bad_email = 'marcin@wrongmail.org'
403 # this initializes a session
415 # this initializes a session
404 self.app.get(route_path('reset_password'))
416 self.app.get(route_path('reset_password'))
405
417
406 response = self.app.post(
418 response = self.app.post(
407 route_path('reset_password'), {'email': bad_email, }
419 route_path('reset_password'), {'email': bad_email, }
408 )
420 )
409 assert_session_flash(response,
421 assert_session_flash(response,
410 'If such email exists, a password reset link was sent to it.')
422 'If such email exists, a password reset link was sent to it.')
411
423
412 def test_forgot_password(self, user_util):
424 def test_forgot_password(self, user_util):
413 # this initializes a session
425 # this initializes a session
414 self.app.get(route_path('reset_password'))
426 self.app.get(route_path('reset_password'))
415
427
416 user = user_util.create_user()
428 user = user_util.create_user()
417 user_id = user.user_id
429 user_id = user.user_id
418 email = user.email
430 email = user.email
419
431
420 response = self.app.post(route_path('reset_password'), {'email': email, })
432 response = self.app.post(route_path('reset_password'), {'email': email, })
421
433
422 assert_session_flash(response,
434 assert_session_flash(response,
423 'If such email exists, a password reset link was sent to it.')
435 'If such email exists, a password reset link was sent to it.')
424
436
425 # BAD KEY
437 # BAD KEY
426 confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), 'badkey')
438 confirm_url = route_path('reset_password_confirmation', params={'key': 'badkey'})
427 response = self.app.get(confirm_url, status=302)
439 response = self.app.get(confirm_url, status=302)
428 assert response.location.endswith(route_path('reset_password'))
440 assert response.location.endswith(route_path('reset_password'))
429 assert_session_flash(response, 'Given reset token is invalid')
441 assert_session_flash(response, 'Given reset token is invalid')
430
442
431 response.follow() # cleanup flash
443 response.follow() # cleanup flash
432
444
433 # GOOD KEY
445 # GOOD KEY
434 key = UserApiKeys.query()\
446 key = UserApiKeys.query()\
435 .filter(UserApiKeys.user_id == user_id)\
447 .filter(UserApiKeys.user_id == user_id)\
436 .filter(UserApiKeys.role == UserApiKeys.ROLE_PASSWORD_RESET)\
448 .filter(UserApiKeys.role == UserApiKeys.ROLE_PASSWORD_RESET)\
437 .first()
449 .first()
438
450
439 assert key
451 assert key
440
452
441 confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), key.api_key)
453 confirm_url = '{}?key={}'.format(route_path('reset_password_confirmation'), key.api_key)
442 response = self.app.get(confirm_url)
454 response = self.app.get(confirm_url)
443 assert response.status == '302 Found'
455 assert response.status == '302 Found'
444 assert response.location.endswith(route_path('login'))
456 assert response.location.endswith(route_path('login'))
445
457
446 assert_session_flash(
458 assert_session_flash(
447 response,
459 response,
448 'Your password reset was successful, '
460 'Your password reset was successful, '
449 'a new password has been sent to your email')
461 'a new password has been sent to your email')
450
462
451 response.follow()
463 response.follow()
452
464
453 def _get_api_whitelist(self, values=None):
465 def _get_api_whitelist(self, values=None):
454 config = {'api_access_controllers_whitelist': values or []}
466 config = {'api_access_controllers_whitelist': values or []}
455 return config
467 return config
456
468
457 @pytest.mark.parametrize("test_name, auth_token", [
469 @pytest.mark.parametrize("test_name, auth_token", [
458 ('none', None),
470 ('none', None),
459 ('empty_string', ''),
471 ('empty_string', ''),
460 ('fake_number', '123456'),
472 ('fake_number', '123456'),
461 ('proper_auth_token', None)
473 ('proper_auth_token', None)
462 ])
474 ])
463 def test_access_not_whitelisted_page_via_auth_token(
475 def test_access_not_whitelisted_page_via_auth_token(
464 self, test_name, auth_token, user_admin):
476 self, test_name, auth_token, user_admin):
465
477
466 whitelist = self._get_api_whitelist([])
478 whitelist = self._get_api_whitelist([])
467 with mock.patch.dict('rhodecode.CONFIG', whitelist):
479 with mock.patch.dict('rhodecode.CONFIG', whitelist):
468 assert [] == whitelist['api_access_controllers_whitelist']
480 assert [] == whitelist['api_access_controllers_whitelist']
469 if test_name == 'proper_auth_token':
481 if test_name == 'proper_auth_token':
470 # use builtin if api_key is None
482 # use builtin if api_key is None
471 auth_token = user_admin.api_key
483 auth_token = user_admin.api_key
472
484
473 with fixture.anon_access(False):
485 with fixture.anon_access(False):
474 # webtest uses linter to check if response is bytes,
486 # webtest uses linter to check if response is bytes,
475 # and we use memoryview here as a wrapper, quick turn-off
487 # and we use memoryview here as a wrapper, quick turn-off
476 self.app.lint = False
488 self.app.lint = False
477
489
478 self.app.get(
490 self.app.get(
479 route_path('repo_commit_raw',
491 route_path('repo_commit_raw',
480 repo_name=HG_REPO, commit_id='tip',
492 repo_name=HG_REPO, commit_id='tip',
481 params=dict(api_key=auth_token)),
493 params=dict(api_key=auth_token)),
482 status=302)
494 status=302)
483
495
484 @pytest.mark.parametrize("test_name, auth_token, code", [
496 @pytest.mark.parametrize("test_name, auth_token, code", [
485 ('none', None, 302),
497 ('none', None, 302),
486 ('empty_string', '', 302),
498 ('empty_string', '', 302),
487 ('fake_number', '123456', 302),
499 ('fake_number', '123456', 302),
488 ('proper_auth_token', None, 200)
500 ('proper_auth_token', None, 200)
489 ])
501 ])
490 def test_access_whitelisted_page_via_auth_token(
502 def test_access_whitelisted_page_via_auth_token(
491 self, test_name, auth_token, code, user_admin):
503 self, test_name, auth_token, code, user_admin):
492
504
493 whitelist = self._get_api_whitelist(whitelist_view)
505 whitelist = self._get_api_whitelist(whitelist_view)
494
506
495 with mock.patch.dict('rhodecode.CONFIG', whitelist):
507 with mock.patch.dict('rhodecode.CONFIG', whitelist):
496 assert whitelist_view == whitelist['api_access_controllers_whitelist']
508 assert whitelist_view == whitelist['api_access_controllers_whitelist']
497
509
498 if test_name == 'proper_auth_token':
510 if test_name == 'proper_auth_token':
499 auth_token = user_admin.api_key
511 auth_token = user_admin.api_key
500 assert auth_token
512 assert auth_token
501
513
502 with fixture.anon_access(False):
514 with fixture.anon_access(False):
503 # webtest uses linter to check if response is bytes,
515 # webtest uses linter to check if response is bytes,
504 # and we use memoryview here as a wrapper, quick turn-off
516 # and we use memoryview here as a wrapper, quick turn-off
505 self.app.lint = False
517 self.app.lint = False
506 self.app.get(
518 self.app.get(
507 route_path('repo_commit_raw',
519 route_path('repo_commit_raw',
508 repo_name=HG_REPO, commit_id='tip',
520 repo_name=HG_REPO, commit_id='tip',
509 params=dict(api_key=auth_token)),
521 params=dict(api_key=auth_token)),
510 status=code)
522 status=code)
511
523
512 @pytest.mark.parametrize("test_name, auth_token, code", [
524 @pytest.mark.parametrize("test_name, auth_token, code", [
513 ('proper_auth_token', None, 200),
525 ('proper_auth_token', None, 200),
514 ('wrong_auth_token', '123456', 302),
526 ('wrong_auth_token', '123456', 302),
515 ])
527 ])
516 def test_access_whitelisted_page_via_auth_token_bound_to_token(
528 def test_access_whitelisted_page_via_auth_token_bound_to_token(
517 self, test_name, auth_token, code, user_admin):
529 self, test_name, auth_token, code, user_admin):
518
530
519 expected_token = auth_token
531 expected_token = auth_token
520 if test_name == 'proper_auth_token':
532 if test_name == 'proper_auth_token':
521 auth_token = user_admin.api_key
533 auth_token = user_admin.api_key
522 expected_token = auth_token
534 expected_token = auth_token
523 assert auth_token
535 assert auth_token
524
536
525 whitelist = self._get_api_whitelist([
537 whitelist = self._get_api_whitelist([
526 'RepoCommitsView:repo_commit_raw@{}'.format(expected_token)])
538 'RepoCommitsView:repo_commit_raw@{}'.format(expected_token)])
527
539
528 with mock.patch.dict('rhodecode.CONFIG', whitelist):
540 with mock.patch.dict('rhodecode.CONFIG', whitelist):
529
541
530 with fixture.anon_access(False):
542 with fixture.anon_access(False):
531 # webtest uses linter to check if response is bytes,
543 # webtest uses linter to check if response is bytes,
532 # and we use memoryview here as a wrapper, quick turn-off
544 # and we use memoryview here as a wrapper, quick turn-off
533 self.app.lint = False
545 self.app.lint = False
534
546
535 self.app.get(
547 self.app.get(
536 route_path('repo_commit_raw',
548 route_path('repo_commit_raw',
537 repo_name=HG_REPO, commit_id='tip',
549 repo_name=HG_REPO, commit_id='tip',
538 params=dict(api_key=auth_token)),
550 params=dict(api_key=auth_token)),
539 status=code)
551 status=code)
540
552
541 def test_access_page_via_extra_auth_token(self):
553 def test_access_page_via_extra_auth_token(self):
542 whitelist = self._get_api_whitelist(whitelist_view)
554 whitelist = self._get_api_whitelist(whitelist_view)
543 with mock.patch.dict('rhodecode.CONFIG', whitelist):
555 with mock.patch.dict('rhodecode.CONFIG', whitelist):
544 assert whitelist_view == \
556 assert whitelist_view == \
545 whitelist['api_access_controllers_whitelist']
557 whitelist['api_access_controllers_whitelist']
546
558
547 new_auth_token = AuthTokenModel().create(
559 new_auth_token = AuthTokenModel().create(
548 TEST_USER_ADMIN_LOGIN, 'test')
560 TEST_USER_ADMIN_LOGIN, 'test')
549 Session().commit()
561 Session().commit()
550 with fixture.anon_access(False):
562 with fixture.anon_access(False):
551 # webtest uses linter to check if response is bytes,
563 # webtest uses linter to check if response is bytes,
552 # and we use memoryview here as a wrapper, quick turn-off
564 # and we use memoryview here as a wrapper, quick turn-off
553 self.app.lint = False
565 self.app.lint = False
554 self.app.get(
566 self.app.get(
555 route_path('repo_commit_raw',
567 route_path('repo_commit_raw',
556 repo_name=HG_REPO, commit_id='tip',
568 repo_name=HG_REPO, commit_id='tip',
557 params=dict(api_key=new_auth_token.api_key)),
569 params=dict(api_key=new_auth_token.api_key)),
558 status=200)
570 status=200)
559
571
560 def test_access_page_via_expired_auth_token(self):
572 def test_access_page_via_expired_auth_token(self):
561 whitelist = self._get_api_whitelist(whitelist_view)
573 whitelist = self._get_api_whitelist(whitelist_view)
562 with mock.patch.dict('rhodecode.CONFIG', whitelist):
574 with mock.patch.dict('rhodecode.CONFIG', whitelist):
563 assert whitelist_view == \
575 assert whitelist_view == \
564 whitelist['api_access_controllers_whitelist']
576 whitelist['api_access_controllers_whitelist']
565
577
566 new_auth_token = AuthTokenModel().create(
578 new_auth_token = AuthTokenModel().create(
567 TEST_USER_ADMIN_LOGIN, 'test')
579 TEST_USER_ADMIN_LOGIN, 'test')
568 Session().commit()
580 Session().commit()
569 # patch the api key and make it expired
581 # patch the api key and make it expired
570 new_auth_token.expires = 0
582 new_auth_token.expires = 0
571 Session().add(new_auth_token)
583 Session().add(new_auth_token)
572 Session().commit()
584 Session().commit()
573 with fixture.anon_access(False):
585 with fixture.anon_access(False):
574 # webtest uses linter to check if response is bytes,
586 # webtest uses linter to check if response is bytes,
575 # and we use memoryview here as a wrapper, quick turn-off
587 # and we use memoryview here as a wrapper, quick turn-off
576 self.app.lint = False
588 self.app.lint = False
577 self.app.get(
589 self.app.get(
578 route_path('repo_commit_raw',
590 route_path('repo_commit_raw',
579 repo_name=HG_REPO, commit_id='tip',
591 repo_name=HG_REPO, commit_id='tip',
580 params=dict(api_key=new_auth_token.api_key)),
592 params=dict(api_key=new_auth_token.api_key)),
581 status=302)
593 status=302)
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from rhodecode/apps/ssh_support/lib/ssh_wrapper.py to rhodecode/apps/ssh_support/lib/ssh_wrapper_v1.py
NO CONTENT: file renamed from rhodecode/apps/ssh_support/lib/ssh_wrapper.py to rhodecode/apps/ssh_support/lib/ssh_wrapper_v1.py
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from rhodecode/lib/rc_cache/archive_cache.py to rhodecode/lib/archive_cache/backends/fanout_cache.py
NO CONTENT: file renamed from rhodecode/lib/rc_cache/archive_cache.py to rhodecode/lib/archive_cache/backends/fanout_cache.py
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from rhodecode/tests/vcs_operations/test_vcs_operations.py to rhodecode/tests/vcs_operations/test_vcs_operations_git.py
NO CONTENT: file renamed from rhodecode/tests/vcs_operations/test_vcs_operations.py to rhodecode/tests/vcs_operations/test_vcs_operations_git.py
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now