##// END OF EJS Templates
branch-permissions: enabled branch permissions checks for SSH backend.
marcink -
r2982:9342a381 default
parent child Browse files
Show More
@@ -1,208 +1,223 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 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 os
21 import os
22 import re
22 import re
23 import logging
23 import logging
24 import datetime
24 import datetime
25 from pyramid.compat import configparser
25 from pyramid.compat import configparser
26
26
27 from rhodecode.model.db import Session, User, UserSshKeys
27 from rhodecode.model.db import Session, User, UserSshKeys
28 from rhodecode.model.scm import ScmModel
28 from rhodecode.model.scm import ScmModel
29
29
30 from .hg import MercurialServer
30 from .hg import MercurialServer
31 from .git import GitServer
31 from .git import GitServer
32 from .svn import SubversionServer
32 from .svn import SubversionServer
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 class SshWrapper(object):
36 class SshWrapper(object):
37
37
38 def __init__(self, command, connection_info, mode,
38 def __init__(self, command, connection_info, mode,
39 user, user_id, key_id, shell, ini_path, env):
39 user, user_id, key_id, shell, ini_path, env):
40 self.command = command
40 self.command = command
41 self.connection_info = connection_info
41 self.connection_info = connection_info
42 self.mode = mode
42 self.mode = mode
43 self.user = user
43 self.user = user
44 self.user_id = user_id
44 self.user_id = user_id
45 self.key_id = key_id
45 self.key_id = key_id
46 self.shell = shell
46 self.shell = shell
47 self.ini_path = ini_path
47 self.ini_path = ini_path
48 self.env = env
48 self.env = env
49
49
50 self.config = self.parse_config(ini_path)
50 self.config = self.parse_config(ini_path)
51 self.server_impl = None
51 self.server_impl = None
52
52
53 def parse_config(self, config_path):
53 def parse_config(self, config_path):
54 parser = configparser.ConfigParser()
54 parser = configparser.ConfigParser()
55 parser.read(config_path)
55 parser.read(config_path)
56 return parser
56 return parser
57
57
58 def update_key_access_time(self, key_id):
58 def update_key_access_time(self, key_id):
59 key = UserSshKeys().query().filter(
59 key = UserSshKeys().query().filter(
60 UserSshKeys.ssh_key_id == key_id).scalar()
60 UserSshKeys.ssh_key_id == key_id).scalar()
61 if key:
61 if key:
62 key.accessed_on = datetime.datetime.utcnow()
62 key.accessed_on = datetime.datetime.utcnow()
63 Session().add(key)
63 Session().add(key)
64 Session().commit()
64 Session().commit()
65 log.debug('Update key id:`%s` fingerprint:`%s` access time',
65 log.debug('Update key id:`%s` fingerprint:`%s` access time',
66 key_id, key.ssh_key_fingerprint)
66 key_id, key.ssh_key_fingerprint)
67
67
68 def get_connection_info(self):
68 def get_connection_info(self):
69 """
69 """
70 connection_info
70 connection_info
71
71
72 Identifies the client and server ends of the connection.
72 Identifies the client and server ends of the connection.
73 The variable contains four space-separated values: client IP address,
73 The variable contains four space-separated values: client IP address,
74 client port number, server IP address, and server port number.
74 client port number, server IP address, and server port number.
75 """
75 """
76 conn = dict(
76 conn = dict(
77 client_ip=None,
77 client_ip=None,
78 client_port=None,
78 client_port=None,
79 server_ip=None,
79 server_ip=None,
80 server_port=None,
80 server_port=None,
81 )
81 )
82
82
83 info = self.connection_info.split(' ')
83 info = self.connection_info.split(' ')
84 if len(info) == 4:
84 if len(info) == 4:
85 conn['client_ip'] = info[0]
85 conn['client_ip'] = info[0]
86 conn['client_port'] = info[1]
86 conn['client_port'] = info[1]
87 conn['server_ip'] = info[2]
87 conn['server_ip'] = info[2]
88 conn['server_port'] = info[3]
88 conn['server_port'] = info[3]
89
89
90 return conn
90 return conn
91
91
92 def get_repo_details(self, mode):
92 def get_repo_details(self, mode):
93 vcs_type = mode if mode in ['svn', 'hg', 'git'] else None
93 vcs_type = mode if mode in ['svn', 'hg', 'git'] else None
94 mode = mode
94 mode = mode
95 repo_name = None
95 repo_name = None
96
96
97 hg_pattern = r'^hg\s+\-R\s+(\S+)\s+serve\s+\-\-stdio$'
97 hg_pattern = r'^hg\s+\-R\s+(\S+)\s+serve\s+\-\-stdio$'
98 hg_match = re.match(hg_pattern, self.command)
98 hg_match = re.match(hg_pattern, self.command)
99 if hg_match is not None:
99 if hg_match is not None:
100 vcs_type = 'hg'
100 vcs_type = 'hg'
101 repo_name = hg_match.group(1).strip('/')
101 repo_name = hg_match.group(1).strip('/')
102 return vcs_type, repo_name, mode
102 return vcs_type, repo_name, mode
103
103
104 git_pattern = (
104 git_pattern = (
105 r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$')
105 r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$')
106 git_match = re.match(git_pattern, self.command)
106 git_match = re.match(git_pattern, self.command)
107 if git_match is not None:
107 if git_match is not None:
108 vcs_type = 'git'
108 vcs_type = 'git'
109 repo_name = git_match.group(2).strip('/')
109 repo_name = git_match.group(2).strip('/')
110 mode = git_match.group(1)
110 mode = git_match.group(1)
111 return vcs_type, repo_name, mode
111 return vcs_type, repo_name, mode
112
112
113 svn_pattern = r'^svnserve -t'
113 svn_pattern = r'^svnserve -t'
114 svn_match = re.match(svn_pattern, self.command)
114 svn_match = re.match(svn_pattern, self.command)
115
115
116 if svn_match is not None:
116 if svn_match is not None:
117 vcs_type = 'svn'
117 vcs_type = 'svn'
118 # Repo name should be extracted from the input stream
118 # Repo name should be extracted from the input stream
119 return vcs_type, repo_name, mode
119 return vcs_type, repo_name, mode
120
120
121 return vcs_type, repo_name, mode
121 return vcs_type, repo_name, mode
122
122
123 def serve(self, vcs, repo, mode, user, permissions):
123 def serve(self, vcs, repo, mode, user, permissions, branch_permissions):
124 store = ScmModel().repos_path
124 store = ScmModel().repos_path
125
125
126 check_branch_perms = False
127 detect_force_push = False
128
129 if branch_permissions:
130 check_branch_perms = True
131 detect_force_push = True
132
126 log.debug(
133 log.debug(
127 'VCS detected:`%s` mode: `%s` repo_name: %s', vcs, mode, repo)
134 'VCS detected:`%s` mode: `%s` repo_name: %s, branch_permission_checks:%s',
135 vcs, mode, repo, check_branch_perms)
136
137 # detect if we have to check branch permissions
138 extras = {
139 'detect_force_push': detect_force_push,
140 'check_branch_perms': check_branch_perms,
141 }
128
142
129 if vcs == 'hg':
143 if vcs == 'hg':
130 server = MercurialServer(
144 server = MercurialServer(
131 store=store, ini_path=self.ini_path,
145 store=store, ini_path=self.ini_path,
132 repo_name=repo, user=user,
146 repo_name=repo, user=user,
133 user_permissions=permissions, config=self.config, env=self.env)
147 user_permissions=permissions, config=self.config, env=self.env)
134 self.server_impl = server
148 self.server_impl = server
135 return server.run()
149 return server.run(tunnel_extras=extras)
136
150
137 elif vcs == 'git':
151 elif vcs == 'git':
138 server = GitServer(
152 server = GitServer(
139 store=store, ini_path=self.ini_path,
153 store=store, ini_path=self.ini_path,
140 repo_name=repo, repo_mode=mode, user=user,
154 repo_name=repo, repo_mode=mode, user=user,
141 user_permissions=permissions, config=self.config, env=self.env)
155 user_permissions=permissions, config=self.config, env=self.env)
142 self.server_impl = server
156 self.server_impl = server
143 return server.run()
157 return server.run(tunnel_extras=extras)
144
158
145 elif vcs == 'svn':
159 elif vcs == 'svn':
146 server = SubversionServer(
160 server = SubversionServer(
147 store=store, ini_path=self.ini_path,
161 store=store, ini_path=self.ini_path,
148 repo_name=None, user=user,
162 repo_name=None, user=user,
149 user_permissions=permissions, config=self.config, env=self.env)
163 user_permissions=permissions, config=self.config, env=self.env)
150 self.server_impl = server
164 self.server_impl = server
151 return server.run()
165 return server.run(tunnel_extras=extras)
152
166
153 else:
167 else:
154 raise Exception('Unrecognised VCS: {}'.format(vcs))
168 raise Exception('Unrecognised VCS: {}'.format(vcs))
155
169
156 def wrap(self):
170 def wrap(self):
157 mode = self.mode
171 mode = self.mode
158 user = self.user
172 user = self.user
159 user_id = self.user_id
173 user_id = self.user_id
160 key_id = self.key_id
174 key_id = self.key_id
161 shell = self.shell
175 shell = self.shell
162
176
163 scm_detected, scm_repo, scm_mode = self.get_repo_details(mode)
177 scm_detected, scm_repo, scm_mode = self.get_repo_details(mode)
164
178
165 log.debug(
179 log.debug(
166 'Mode: `%s` User: `%s:%s` Shell: `%s` SSH Command: `\"%s\"` '
180 'Mode: `%s` User: `%s:%s` Shell: `%s` SSH Command: `\"%s\"` '
167 'SCM_DETECTED: `%s` SCM Mode: `%s` SCM Repo: `%s`',
181 'SCM_DETECTED: `%s` SCM Mode: `%s` SCM Repo: `%s`',
168 mode, user, user_id, shell, self.command,
182 mode, user, user_id, shell, self.command,
169 scm_detected, scm_mode, scm_repo)
183 scm_detected, scm_mode, scm_repo)
170
184
171 # update last access time for this key
185 # update last access time for this key
172 self.update_key_access_time(key_id)
186 self.update_key_access_time(key_id)
173
187
174 log.debug('SSH Connection info %s', self.get_connection_info())
188 log.debug('SSH Connection info %s', self.get_connection_info())
175
189
176 if shell and self.command is None:
190 if shell and self.command is None:
177 log.info(
191 log.info(
178 'Dropping to shell, no command given and shell is allowed')
192 'Dropping to shell, no command given and shell is allowed')
179 os.execl('/bin/bash', '-l')
193 os.execl('/bin/bash', '-l')
180 exit_code = 1
194 exit_code = 1
181
195
182 elif scm_detected:
196 elif scm_detected:
183 user = User.get(user_id)
197 user = User.get(user_id)
184 if not user:
198 if not user:
185 log.warning('User with id %s not found', user_id)
199 log.warning('User with id %s not found', user_id)
186 exit_code = -1
200 exit_code = -1
187 return exit_code
201 return exit_code
188
202
189 auth_user = user.AuthUser()
203 auth_user = user.AuthUser()
190 permissions = auth_user.permissions['repositories']
204 permissions = auth_user.permissions['repositories']
191
205 repo_branch_permissions = auth_user.get_branch_permissions(scm_repo)
192 try:
206 try:
193 exit_code, is_updated = self.serve(
207 exit_code, is_updated = self.serve(
194 scm_detected, scm_repo, scm_mode, user, permissions)
208 scm_detected, scm_repo, scm_mode, user, permissions,
209 repo_branch_permissions)
195 except Exception:
210 except Exception:
196 log.exception('Error occurred during execution of SshWrapper')
211 log.exception('Error occurred during execution of SshWrapper')
197 exit_code = -1
212 exit_code = -1
198
213
199 elif self.command is None and shell is False:
214 elif self.command is None and shell is False:
200 log.error('No Command given.')
215 log.error('No Command given.')
201 exit_code = -1
216 exit_code = -1
202
217
203 else:
218 else:
204 log.error(
219 log.error(
205 'Unhandled Command: "%s" Aborting.', self.command)
220 'Unhandled Command: "%s" Aborting.', self.command)
206 exit_code = -1
221 exit_code = -1
207
222
208 return exit_code
223 return exit_code
@@ -1,156 +1,162 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 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 os
21 import os
22 import sys
22 import sys
23 import json
23 import json
24 import logging
24 import logging
25
25
26 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
26 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
27 from rhodecode.lib.vcs.conf import settings as vcs_settings
27 from rhodecode.lib.vcs.conf import settings as vcs_settings
28 from rhodecode.model.scm import ScmModel
28 from rhodecode.model.scm import ScmModel
29
29
30 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
31
31
32
32
33 class VcsServer(object):
33 class VcsServer(object):
34 _path = None # set executable path for hg/git/svn binary
34 _path = None # set executable path for hg/git/svn binary
35 backend = None # set in child classes
35 backend = None # set in child classes
36 tunnel = None # subprocess handling tunnel
36 tunnel = None # subprocess handling tunnel
37 write_perms = ['repository.admin', 'repository.write']
37 write_perms = ['repository.admin', 'repository.write']
38 read_perms = ['repository.read', 'repository.admin', 'repository.write']
38 read_perms = ['repository.read', 'repository.admin', 'repository.write']
39
39
40 def __init__(self, user, user_permissions, config, env):
40 def __init__(self, user, user_permissions, config, env):
41 self.user = user
41 self.user = user
42 self.user_permissions = user_permissions
42 self.user_permissions = user_permissions
43 self.config = config
43 self.config = config
44 self.env = env
44 self.env = env
45 self.stdin = sys.stdin
45 self.stdin = sys.stdin
46
46
47 self.repo_name = None
47 self.repo_name = None
48 self.repo_mode = None
48 self.repo_mode = None
49 self.store = ''
49 self.store = ''
50 self.ini_path = ''
50 self.ini_path = ''
51
51
52 def _invalidate_cache(self, repo_name):
52 def _invalidate_cache(self, repo_name):
53 """
53 """
54 Set's cache for this repository for invalidation on next access
54 Set's cache for this repository for invalidation on next access
55
55
56 :param repo_name: full repo name, also a cache key
56 :param repo_name: full repo name, also a cache key
57 """
57 """
58 ScmModel().mark_for_invalidation(repo_name)
58 ScmModel().mark_for_invalidation(repo_name)
59
59
60 def has_write_perm(self):
60 def has_write_perm(self):
61 permission = self.user_permissions.get(self.repo_name)
61 permission = self.user_permissions.get(self.repo_name)
62 if permission in ['repository.write', 'repository.admin']:
62 if permission in ['repository.write', 'repository.admin']:
63 return True
63 return True
64
64
65 return False
65 return False
66
66
67 def _check_permissions(self, action):
67 def _check_permissions(self, action):
68 permission = self.user_permissions.get(self.repo_name)
68 permission = self.user_permissions.get(self.repo_name)
69 log.debug(
69 log.debug(
70 'permission for %s on %s are: %s',
70 'permission for %s on %s are: %s',
71 self.user, self.repo_name, permission)
71 self.user, self.repo_name, permission)
72
72
73 if not permission:
73 if not permission:
74 log.error('user `%s` permissions to repo:%s are empty. Forbidding access.',
74 log.error('user `%s` permissions to repo:%s are empty. Forbidding access.',
75 self.user, self.repo_name)
75 self.user, self.repo_name)
76 return -2
76 return -2
77
77
78 if action == 'pull':
78 if action == 'pull':
79 if permission in self.read_perms:
79 if permission in self.read_perms:
80 log.info(
80 log.info(
81 'READ Permissions for User "%s" detected to repo "%s"!',
81 'READ Permissions for User "%s" detected to repo "%s"!',
82 self.user, self.repo_name)
82 self.user, self.repo_name)
83 return 0
83 return 0
84 else:
84 else:
85 if permission in self.write_perms:
85 if permission in self.write_perms:
86 log.info(
86 log.info(
87 'WRITE+ Permissions for User "%s" detected to repo "%s"!',
87 'WRITE+ Permissions for User "%s" detected to repo "%s"!',
88 self.user, self.repo_name)
88 self.user, self.repo_name)
89 return 0
89 return 0
90
90
91 log.error('Cannot properly fetch or verify user `%s` permissions. '
91 log.error('Cannot properly fetch or verify user `%s` permissions. '
92 'Permissions: %s, vcs action: %s',
92 'Permissions: %s, vcs action: %s',
93 self.user, permission, action)
93 self.user, permission, action)
94 return -2
94 return -2
95
95
96 def update_environment(self, action, extras=None):
96 def update_environment(self, action, extras=None):
97
97
98 scm_data = {
98 scm_data = {
99 'ip': os.environ['SSH_CLIENT'].split()[0],
99 'ip': os.environ['SSH_CLIENT'].split()[0],
100 'username': self.user.username,
100 'username': self.user.username,
101 'user_id': self.user.user_id,
101 'user_id': self.user.user_id,
102 'action': action,
102 'action': action,
103 'repository': self.repo_name,
103 'repository': self.repo_name,
104 'scm': self.backend,
104 'scm': self.backend,
105 'config': self.ini_path,
105 'config': self.ini_path,
106 'make_lock': None,
106 'make_lock': None,
107 'locked_by': [None, None],
107 'locked_by': [None, None],
108 'server_url': None,
108 'server_url': None,
109 'is_shadow_repo': False,
109 'user_agent': 'ssh-user-agent',
110 'hooks_module': 'rhodecode.lib.hooks_daemon',
111 'hooks': ['push', 'pull'],
110 'hooks': ['push', 'pull'],
111 'hooks_module': 'rhodecode.lib.hooks_daemon',
112 'is_shadow_repo': False,
113 'detect_force_push': False,
114 'check_branch_perms': False,
115
112 'SSH': True,
116 'SSH': True,
113 'SSH_PERMISSIONS': self.user_permissions.get(self.repo_name)
117 'SSH_PERMISSIONS': self.user_permissions.get(self.repo_name),
114 }
118 }
115 if extras:
119 if extras:
116 scm_data.update(extras)
120 scm_data.update(extras)
117 os.putenv("RC_SCM_DATA", json.dumps(scm_data))
121 os.putenv("RC_SCM_DATA", json.dumps(scm_data))
118
122
119 def get_root_store(self):
123 def get_root_store(self):
120 root_store = self.store
124 root_store = self.store
121 if not root_store.endswith('/'):
125 if not root_store.endswith('/'):
122 # always append trailing slash
126 # always append trailing slash
123 root_store = root_store + '/'
127 root_store = root_store + '/'
124 return root_store
128 return root_store
125
129
126 def _handle_tunnel(self, extras):
130 def _handle_tunnel(self, extras):
127 # pre-auth
131 # pre-auth
128 action = 'pull'
132 action = 'pull'
129 exit_code = self._check_permissions(action)
133 exit_code = self._check_permissions(action)
130 if exit_code:
134 if exit_code:
131 return exit_code, False
135 return exit_code, False
132
136
133 req = self.env['request']
137 req = self.env['request']
134 server_url = req.host_url + req.script_name
138 server_url = req.host_url + req.script_name
135 extras['server_url'] = server_url
139 extras['server_url'] = server_url
136
140
137 log.debug('Using %s binaries from path %s', self.backend, self._path)
141 log.debug('Using %s binaries from path %s', self.backend, self._path)
138 exit_code = self.tunnel.run(extras)
142 exit_code = self.tunnel.run(extras)
139
143
140 return exit_code, action == "push"
144 return exit_code, action == "push"
141
145
142 def run(self):
146 def run(self, tunnel_extras=None):
147 tunnel_extras = tunnel_extras or {}
143 extras = {}
148 extras = {}
149 extras.update(tunnel_extras)
144
150
145 callback_daemon, extras = prepare_callback_daemon(
151 callback_daemon, extras = prepare_callback_daemon(
146 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
152 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
147 host=vcs_settings.HOOKS_HOST,
153 host=vcs_settings.HOOKS_HOST,
148 use_direct_calls=False)
154 use_direct_calls=False)
149
155
150 with callback_daemon:
156 with callback_daemon:
151 try:
157 try:
152 return self._handle_tunnel(extras)
158 return self._handle_tunnel(extras)
153 finally:
159 finally:
154 log.debug('Running cleanup with cache invalidation')
160 log.debug('Running cleanup with cache invalidation')
155 if self.repo_name:
161 if self.repo_name:
156 self._invalidate_cache(self.repo_name)
162 self._invalidate_cache(self.repo_name)
@@ -1,146 +1,149 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 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 json
21 import json
22 import mock
22 import mock
23 import pytest
23 import pytest
24
24
25 from rhodecode.apps.ssh_support.lib.backends.git import GitServer
25 from rhodecode.apps.ssh_support.lib.backends.git import GitServer
26 from rhodecode.apps.ssh_support.tests.conftest import dummy_env, dummy_user
26 from rhodecode.apps.ssh_support.tests.conftest import dummy_env, dummy_user
27
27
28
28
29 class GitServerCreator(object):
29 class GitServerCreator(object):
30 root = '/tmp/repo/path/'
30 root = '/tmp/repo/path/'
31 git_path = '/usr/local/bin/git'
31 git_path = '/usr/local/bin/git'
32 config_data = {
32 config_data = {
33 'app:main': {
33 'app:main': {
34 'ssh.executable.git': git_path,
34 'ssh.executable.git': git_path,
35 'vcs.hooks.protocol': 'http',
35 'vcs.hooks.protocol': 'http',
36 }
36 }
37 }
37 }
38 repo_name = 'test_git'
38 repo_name = 'test_git'
39 repo_mode = 'receive-pack'
39 repo_mode = 'receive-pack'
40 user = dummy_user()
40 user = dummy_user()
41
41
42 def __init__(self):
42 def __init__(self):
43 def config_get(part, key):
43 def config_get(part, key):
44 return self.config_data.get(part, {}).get(key)
44 return self.config_data.get(part, {}).get(key)
45 self.config_mock = mock.Mock()
45 self.config_mock = mock.Mock()
46 self.config_mock.get = mock.Mock(side_effect=config_get)
46 self.config_mock.get = mock.Mock(side_effect=config_get)
47
47
48 def create(self, **kwargs):
48 def create(self, **kwargs):
49 parameters = {
49 parameters = {
50 'store': self.root,
50 'store': self.root,
51 'ini_path': '',
51 'ini_path': '',
52 'user': self.user,
52 'user': self.user,
53 'repo_name': self.repo_name,
53 'repo_name': self.repo_name,
54 'repo_mode': self.repo_mode,
54 'repo_mode': self.repo_mode,
55 'user_permissions': {
55 'user_permissions': {
56 self.repo_name: 'repository.admin'
56 self.repo_name: 'repository.admin'
57 },
57 },
58 'config': self.config_mock,
58 'config': self.config_mock,
59 'env': dummy_env()
59 'env': dummy_env()
60 }
60 }
61 parameters.update(kwargs)
61 parameters.update(kwargs)
62 server = GitServer(**parameters)
62 server = GitServer(**parameters)
63 return server
63 return server
64
64
65
65
66 @pytest.fixture
66 @pytest.fixture
67 def git_server(app):
67 def git_server(app):
68 return GitServerCreator()
68 return GitServerCreator()
69
69
70
70
71 class TestGitServer(object):
71 class TestGitServer(object):
72
72
73 def test_command(self, git_server):
73 def test_command(self, git_server):
74 server = git_server.create()
74 server = git_server.create()
75 expected_command = (
75 expected_command = (
76 'cd {root}; {git_path} {repo_mode} \'{root}{repo_name}\''.format(
76 'cd {root}; {git_path} {repo_mode} \'{root}{repo_name}\''.format(
77 root=git_server.root, git_path=git_server.git_path,
77 root=git_server.root, git_path=git_server.git_path,
78 repo_mode=git_server.repo_mode, repo_name=git_server.repo_name)
78 repo_mode=git_server.repo_mode, repo_name=git_server.repo_name)
79 )
79 )
80 assert expected_command == server.tunnel.command()
80 assert expected_command == server.tunnel.command()
81
81
82 @pytest.mark.parametrize('permissions, action, code', [
82 @pytest.mark.parametrize('permissions, action, code', [
83 ({}, 'pull', -2),
83 ({}, 'pull', -2),
84 ({'test_git': 'repository.read'}, 'pull', 0),
84 ({'test_git': 'repository.read'}, 'pull', 0),
85 ({'test_git': 'repository.read'}, 'push', -2),
85 ({'test_git': 'repository.read'}, 'push', -2),
86 ({'test_git': 'repository.write'}, 'push', 0),
86 ({'test_git': 'repository.write'}, 'push', 0),
87 ({'test_git': 'repository.admin'}, 'push', 0),
87 ({'test_git': 'repository.admin'}, 'push', 0),
88
88
89 ])
89 ])
90 def test_permission_checks(self, git_server, permissions, action, code):
90 def test_permission_checks(self, git_server, permissions, action, code):
91 server = git_server.create(user_permissions=permissions)
91 server = git_server.create(user_permissions=permissions)
92 result = server._check_permissions(action)
92 result = server._check_permissions(action)
93 assert result is code
93 assert result is code
94
94
95 @pytest.mark.parametrize('permissions, value', [
95 @pytest.mark.parametrize('permissions, value', [
96 ({}, False),
96 ({}, False),
97 ({'test_git': 'repository.read'}, False),
97 ({'test_git': 'repository.read'}, False),
98 ({'test_git': 'repository.write'}, True),
98 ({'test_git': 'repository.write'}, True),
99 ({'test_git': 'repository.admin'}, True),
99 ({'test_git': 'repository.admin'}, True),
100
100
101 ])
101 ])
102 def test_has_write_permissions(self, git_server, permissions, value):
102 def test_has_write_permissions(self, git_server, permissions, value):
103 server = git_server.create(user_permissions=permissions)
103 server = git_server.create(user_permissions=permissions)
104 result = server.has_write_perm()
104 result = server.has_write_perm()
105 assert result is value
105 assert result is value
106
106
107 def test_run_returns_executes_command(self, git_server):
107 def test_run_returns_executes_command(self, git_server):
108 server = git_server.create()
108 server = git_server.create()
109 from rhodecode.apps.ssh_support.lib.backends.git import GitTunnelWrapper
109 from rhodecode.apps.ssh_support.lib.backends.git import GitTunnelWrapper
110 with mock.patch.object(GitTunnelWrapper, 'create_hooks_env') as _patch:
110 with mock.patch.object(GitTunnelWrapper, 'create_hooks_env') as _patch:
111 _patch.return_value = 0
111 _patch.return_value = 0
112 with mock.patch.object(GitTunnelWrapper, 'command', return_value='date'):
112 with mock.patch.object(GitTunnelWrapper, 'command', return_value='date'):
113 exit_code = server.run()
113 exit_code = server.run()
114
114
115 assert exit_code == (0, False)
115 assert exit_code == (0, False)
116
116
117 @pytest.mark.parametrize(
117 @pytest.mark.parametrize(
118 'repo_mode, action', [
118 'repo_mode, action', [
119 ['receive-pack', 'push'],
119 ['receive-pack', 'push'],
120 ['upload-pack', 'pull']
120 ['upload-pack', 'pull']
121 ])
121 ])
122 def test_update_environment(self, git_server, repo_mode, action):
122 def test_update_environment(self, git_server, repo_mode, action):
123 server = git_server.create(repo_mode=repo_mode)
123 server = git_server.create(repo_mode=repo_mode)
124 with mock.patch('os.environ', {'SSH_CLIENT': '10.10.10.10 b'}):
124 with mock.patch('os.environ', {'SSH_CLIENT': '10.10.10.10 b'}):
125 with mock.patch('os.putenv') as putenv_mock:
125 with mock.patch('os.putenv') as putenv_mock:
126 server.update_environment(action)
126 server.update_environment(action)
127
127
128 expected_data = {
128 expected_data = {
129 'username': git_server.user.username,
129 'username': git_server.user.username,
130 'user_id': git_server.user.user_id,
130 'user_id': git_server.user.user_id,
131 'scm': 'git',
131 'scm': 'git',
132 'repository': git_server.repo_name,
132 'repository': git_server.repo_name,
133 'make_lock': None,
133 'make_lock': None,
134 'action': action,
134 'action': action,
135 'ip': '10.10.10.10',
135 'ip': '10.10.10.10',
136 'locked_by': [None, None],
136 'locked_by': [None, None],
137 'config': '',
137 'config': '',
138 'server_url': None,
138 'server_url': None,
139 'hooks': ['push', 'pull'],
139 'hooks': ['push', 'pull'],
140 'is_shadow_repo': False,
140 'is_shadow_repo': False,
141 'hooks_module': 'rhodecode.lib.hooks_daemon',
141 'hooks_module': 'rhodecode.lib.hooks_daemon',
142 'check_branch_perms': False,
143 'detect_force_push': False,
144 'user_agent': u'ssh-user-agent',
142 'SSH': True,
145 'SSH': True,
143 'SSH_PERMISSIONS': 'repository.admin',
146 'SSH_PERMISSIONS': 'repository.admin',
144 }
147 }
145 args, kwargs = putenv_mock.call_args
148 args, kwargs = putenv_mock.call_args
146 assert json.loads(args[1]) == expected_data
149 assert json.loads(args[1]) == expected_data
@@ -1,54 +1,54 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 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 pytest
21 import pytest
22
22
23
23
24 class TestSSHWrapper(object):
24 class TestSSHWrapper(object):
25
25
26 def test_serve_raises_an_exception_when_vcs_is_not_recognized(self, ssh_wrapper):
26 def test_serve_raises_an_exception_when_vcs_is_not_recognized(self, ssh_wrapper):
27 with pytest.raises(Exception) as exc_info:
27 with pytest.raises(Exception) as exc_info:
28 ssh_wrapper.serve(
28 ssh_wrapper.serve(
29 vcs='microsoft-tfs', repo='test-repo', mode=None, user='test',
29 vcs='microsoft-tfs', repo='test-repo', mode=None, user='test',
30 permissions={})
30 permissions={}, branch_permissions={})
31 assert exc_info.value.message == 'Unrecognised VCS: microsoft-tfs'
31 assert exc_info.value.message == 'Unrecognised VCS: microsoft-tfs'
32
32
33 def test_parse_config(self, ssh_wrapper):
33 def test_parse_config(self, ssh_wrapper):
34 config = ssh_wrapper.parse_config(ssh_wrapper.ini_path)
34 config = ssh_wrapper.parse_config(ssh_wrapper.ini_path)
35 assert config
35 assert config
36
36
37 def test_get_connection_info(self, ssh_wrapper):
37 def test_get_connection_info(self, ssh_wrapper):
38 conn_info = ssh_wrapper.get_connection_info()
38 conn_info = ssh_wrapper.get_connection_info()
39 assert {'client_ip': '127.0.0.1',
39 assert {'client_ip': '127.0.0.1',
40 'client_port': '22',
40 'client_port': '22',
41 'server_ip': '10.0.0.1',
41 'server_ip': '10.0.0.1',
42 'server_port': '443'} == conn_info
42 'server_port': '443'} == conn_info
43
43
44 @pytest.mark.parametrize('command, vcs', [
44 @pytest.mark.parametrize('command, vcs', [
45 ('xxx', None),
45 ('xxx', None),
46 ('svnserve -t', 'svn'),
46 ('svnserve -t', 'svn'),
47 ('hg -R repo serve --stdio', 'hg'),
47 ('hg -R repo serve --stdio', 'hg'),
48 ('git-receive-pack \'repo.git\'', 'git'),
48 ('git-receive-pack \'repo.git\'', 'git'),
49
49
50 ])
50 ])
51 def test_get_repo_details(self, ssh_wrapper, command, vcs):
51 def test_get_repo_details(self, ssh_wrapper, command, vcs):
52 ssh_wrapper.command = command
52 ssh_wrapper.command = command
53 vcs_type, repo_name, mode = ssh_wrapper.get_repo_details(mode='auto')
53 vcs_type, repo_name, mode = ssh_wrapper.get_repo_details(mode='auto')
54 assert vcs_type == vcs
54 assert vcs_type == vcs
@@ -1,2343 +1,2342 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 """
21 """
22 authentication and permission libraries
22 authentication and permission libraries
23 """
23 """
24
24
25 import os
25 import os
26 import time
26 import time
27 import inspect
27 import inspect
28 import collections
28 import collections
29 import fnmatch
29 import fnmatch
30 import hashlib
30 import hashlib
31 import itertools
31 import itertools
32 import logging
32 import logging
33 import random
33 import random
34 import traceback
34 import traceback
35 from functools import wraps
35 from functools import wraps
36
36
37 import ipaddress
37 import ipaddress
38
38
39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
40 from sqlalchemy.orm.exc import ObjectDeletedError
40 from sqlalchemy.orm.exc import ObjectDeletedError
41 from sqlalchemy.orm import joinedload
41 from sqlalchemy.orm import joinedload
42 from zope.cachedescriptors.property import Lazy as LazyProperty
42 from zope.cachedescriptors.property import Lazy as LazyProperty
43
43
44 import rhodecode
44 import rhodecode
45 from rhodecode.model import meta
45 from rhodecode.model import meta
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.model.user import UserModel
47 from rhodecode.model.user import UserModel
48 from rhodecode.model.db import (
48 from rhodecode.model.db import (
49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
51 from rhodecode.lib import rc_cache
51 from rhodecode.lib import rc_cache
52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
53 from rhodecode.lib.utils import (
53 from rhodecode.lib.utils import (
54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
55 from rhodecode.lib.caching_query import FromCache
55 from rhodecode.lib.caching_query import FromCache
56
56
57
57
58 if rhodecode.is_unix:
58 if rhodecode.is_unix:
59 import bcrypt
59 import bcrypt
60
60
61 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
62
62
63 csrf_token_key = "csrf_token"
63 csrf_token_key = "csrf_token"
64
64
65
65
66 class PasswordGenerator(object):
66 class PasswordGenerator(object):
67 """
67 """
68 This is a simple class for generating password from different sets of
68 This is a simple class for generating password from different sets of
69 characters
69 characters
70 usage::
70 usage::
71
71
72 passwd_gen = PasswordGenerator()
72 passwd_gen = PasswordGenerator()
73 #print 8-letter password containing only big and small letters
73 #print 8-letter password containing only big and small letters
74 of alphabet
74 of alphabet
75 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
75 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
76 """
76 """
77 ALPHABETS_NUM = r'''1234567890'''
77 ALPHABETS_NUM = r'''1234567890'''
78 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
78 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
79 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
79 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
80 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
80 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
81 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
81 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
82 + ALPHABETS_NUM + ALPHABETS_SPECIAL
82 + ALPHABETS_NUM + ALPHABETS_SPECIAL
83 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
83 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
84 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
84 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
85 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
85 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
86 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
86 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
87
87
88 def __init__(self, passwd=''):
88 def __init__(self, passwd=''):
89 self.passwd = passwd
89 self.passwd = passwd
90
90
91 def gen_password(self, length, type_=None):
91 def gen_password(self, length, type_=None):
92 if type_ is None:
92 if type_ is None:
93 type_ = self.ALPHABETS_FULL
93 type_ = self.ALPHABETS_FULL
94 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
94 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
95 return self.passwd
95 return self.passwd
96
96
97
97
98 class _RhodeCodeCryptoBase(object):
98 class _RhodeCodeCryptoBase(object):
99 ENC_PREF = None
99 ENC_PREF = None
100
100
101 def hash_create(self, str_):
101 def hash_create(self, str_):
102 """
102 """
103 hash the string using
103 hash the string using
104
104
105 :param str_: password to hash
105 :param str_: password to hash
106 """
106 """
107 raise NotImplementedError
107 raise NotImplementedError
108
108
109 def hash_check_with_upgrade(self, password, hashed):
109 def hash_check_with_upgrade(self, password, hashed):
110 """
110 """
111 Returns tuple in which first element is boolean that states that
111 Returns tuple in which first element is boolean that states that
112 given password matches it's hashed version, and the second is new hash
112 given password matches it's hashed version, and the second is new hash
113 of the password, in case this password should be migrated to new
113 of the password, in case this password should be migrated to new
114 cipher.
114 cipher.
115 """
115 """
116 checked_hash = self.hash_check(password, hashed)
116 checked_hash = self.hash_check(password, hashed)
117 return checked_hash, None
117 return checked_hash, None
118
118
119 def hash_check(self, password, hashed):
119 def hash_check(self, password, hashed):
120 """
120 """
121 Checks matching password with it's hashed value.
121 Checks matching password with it's hashed value.
122
122
123 :param password: password
123 :param password: password
124 :param hashed: password in hashed form
124 :param hashed: password in hashed form
125 """
125 """
126 raise NotImplementedError
126 raise NotImplementedError
127
127
128 def _assert_bytes(self, value):
128 def _assert_bytes(self, value):
129 """
129 """
130 Passing in an `unicode` object can lead to hard to detect issues
130 Passing in an `unicode` object can lead to hard to detect issues
131 if passwords contain non-ascii characters. Doing a type check
131 if passwords contain non-ascii characters. Doing a type check
132 during runtime, so that such mistakes are detected early on.
132 during runtime, so that such mistakes are detected early on.
133 """
133 """
134 if not isinstance(value, str):
134 if not isinstance(value, str):
135 raise TypeError(
135 raise TypeError(
136 "Bytestring required as input, got %r." % (value, ))
136 "Bytestring required as input, got %r." % (value, ))
137
137
138
138
139 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
139 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
140 ENC_PREF = ('$2a$10', '$2b$10')
140 ENC_PREF = ('$2a$10', '$2b$10')
141
141
142 def hash_create(self, str_):
142 def hash_create(self, str_):
143 self._assert_bytes(str_)
143 self._assert_bytes(str_)
144 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
144 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
145
145
146 def hash_check_with_upgrade(self, password, hashed):
146 def hash_check_with_upgrade(self, password, hashed):
147 """
147 """
148 Returns tuple in which first element is boolean that states that
148 Returns tuple in which first element is boolean that states that
149 given password matches it's hashed version, and the second is new hash
149 given password matches it's hashed version, and the second is new hash
150 of the password, in case this password should be migrated to new
150 of the password, in case this password should be migrated to new
151 cipher.
151 cipher.
152
152
153 This implements special upgrade logic which works like that:
153 This implements special upgrade logic which works like that:
154 - check if the given password == bcrypted hash, if yes then we
154 - check if the given password == bcrypted hash, if yes then we
155 properly used password and it was already in bcrypt. Proceed
155 properly used password and it was already in bcrypt. Proceed
156 without any changes
156 without any changes
157 - if bcrypt hash check is not working try with sha256. If hash compare
157 - if bcrypt hash check is not working try with sha256. If hash compare
158 is ok, it means we using correct but old hashed password. indicate
158 is ok, it means we using correct but old hashed password. indicate
159 hash change and proceed
159 hash change and proceed
160 """
160 """
161
161
162 new_hash = None
162 new_hash = None
163
163
164 # regular pw check
164 # regular pw check
165 password_match_bcrypt = self.hash_check(password, hashed)
165 password_match_bcrypt = self.hash_check(password, hashed)
166
166
167 # now we want to know if the password was maybe from sha256
167 # now we want to know if the password was maybe from sha256
168 # basically calling _RhodeCodeCryptoSha256().hash_check()
168 # basically calling _RhodeCodeCryptoSha256().hash_check()
169 if not password_match_bcrypt:
169 if not password_match_bcrypt:
170 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
170 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
171 new_hash = self.hash_create(password) # make new bcrypt hash
171 new_hash = self.hash_create(password) # make new bcrypt hash
172 password_match_bcrypt = True
172 password_match_bcrypt = True
173
173
174 return password_match_bcrypt, new_hash
174 return password_match_bcrypt, new_hash
175
175
176 def hash_check(self, password, hashed):
176 def hash_check(self, password, hashed):
177 """
177 """
178 Checks matching password with it's hashed value.
178 Checks matching password with it's hashed value.
179
179
180 :param password: password
180 :param password: password
181 :param hashed: password in hashed form
181 :param hashed: password in hashed form
182 """
182 """
183 self._assert_bytes(password)
183 self._assert_bytes(password)
184 try:
184 try:
185 return bcrypt.hashpw(password, hashed) == hashed
185 return bcrypt.hashpw(password, hashed) == hashed
186 except ValueError as e:
186 except ValueError as e:
187 # we're having a invalid salt here probably, we should not crash
187 # we're having a invalid salt here probably, we should not crash
188 # just return with False as it would be a wrong password.
188 # just return with False as it would be a wrong password.
189 log.debug('Failed to check password hash using bcrypt %s',
189 log.debug('Failed to check password hash using bcrypt %s',
190 safe_str(e))
190 safe_str(e))
191
191
192 return False
192 return False
193
193
194
194
195 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
195 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
196 ENC_PREF = '_'
196 ENC_PREF = '_'
197
197
198 def hash_create(self, str_):
198 def hash_create(self, str_):
199 self._assert_bytes(str_)
199 self._assert_bytes(str_)
200 return hashlib.sha256(str_).hexdigest()
200 return hashlib.sha256(str_).hexdigest()
201
201
202 def hash_check(self, password, hashed):
202 def hash_check(self, password, hashed):
203 """
203 """
204 Checks matching password with it's hashed value.
204 Checks matching password with it's hashed value.
205
205
206 :param password: password
206 :param password: password
207 :param hashed: password in hashed form
207 :param hashed: password in hashed form
208 """
208 """
209 self._assert_bytes(password)
209 self._assert_bytes(password)
210 return hashlib.sha256(password).hexdigest() == hashed
210 return hashlib.sha256(password).hexdigest() == hashed
211
211
212
212
213 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
213 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
214 ENC_PREF = '_'
214 ENC_PREF = '_'
215
215
216 def hash_create(self, str_):
216 def hash_create(self, str_):
217 self._assert_bytes(str_)
217 self._assert_bytes(str_)
218 return sha1(str_)
218 return sha1(str_)
219
219
220 def hash_check(self, password, hashed):
220 def hash_check(self, password, hashed):
221 """
221 """
222 Checks matching password with it's hashed value.
222 Checks matching password with it's hashed value.
223
223
224 :param password: password
224 :param password: password
225 :param hashed: password in hashed form
225 :param hashed: password in hashed form
226 """
226 """
227 self._assert_bytes(password)
227 self._assert_bytes(password)
228 return sha1(password) == hashed
228 return sha1(password) == hashed
229
229
230
230
231 def crypto_backend():
231 def crypto_backend():
232 """
232 """
233 Return the matching crypto backend.
233 Return the matching crypto backend.
234
234
235 Selection is based on if we run tests or not, we pick sha1-test backend to run
235 Selection is based on if we run tests or not, we pick sha1-test backend to run
236 tests faster since BCRYPT is expensive to calculate
236 tests faster since BCRYPT is expensive to calculate
237 """
237 """
238 if rhodecode.is_test:
238 if rhodecode.is_test:
239 RhodeCodeCrypto = _RhodeCodeCryptoTest()
239 RhodeCodeCrypto = _RhodeCodeCryptoTest()
240 else:
240 else:
241 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
241 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
242
242
243 return RhodeCodeCrypto
243 return RhodeCodeCrypto
244
244
245
245
246 def get_crypt_password(password):
246 def get_crypt_password(password):
247 """
247 """
248 Create the hash of `password` with the active crypto backend.
248 Create the hash of `password` with the active crypto backend.
249
249
250 :param password: The cleartext password.
250 :param password: The cleartext password.
251 :type password: unicode
251 :type password: unicode
252 """
252 """
253 password = safe_str(password)
253 password = safe_str(password)
254 return crypto_backend().hash_create(password)
254 return crypto_backend().hash_create(password)
255
255
256
256
257 def check_password(password, hashed):
257 def check_password(password, hashed):
258 """
258 """
259 Check if the value in `password` matches the hash in `hashed`.
259 Check if the value in `password` matches the hash in `hashed`.
260
260
261 :param password: The cleartext password.
261 :param password: The cleartext password.
262 :type password: unicode
262 :type password: unicode
263
263
264 :param hashed: The expected hashed version of the password.
264 :param hashed: The expected hashed version of the password.
265 :type hashed: The hash has to be passed in in text representation.
265 :type hashed: The hash has to be passed in in text representation.
266 """
266 """
267 password = safe_str(password)
267 password = safe_str(password)
268 return crypto_backend().hash_check(password, hashed)
268 return crypto_backend().hash_check(password, hashed)
269
269
270
270
271 def generate_auth_token(data, salt=None):
271 def generate_auth_token(data, salt=None):
272 """
272 """
273 Generates API KEY from given string
273 Generates API KEY from given string
274 """
274 """
275
275
276 if salt is None:
276 if salt is None:
277 salt = os.urandom(16)
277 salt = os.urandom(16)
278 return hashlib.sha1(safe_str(data) + salt).hexdigest()
278 return hashlib.sha1(safe_str(data) + salt).hexdigest()
279
279
280
280
281 def get_came_from(request):
281 def get_came_from(request):
282 """
282 """
283 get query_string+path from request sanitized after removing auth_token
283 get query_string+path from request sanitized after removing auth_token
284 """
284 """
285 _req = request
285 _req = request
286
286
287 path = _req.path
287 path = _req.path
288 if 'auth_token' in _req.GET:
288 if 'auth_token' in _req.GET:
289 # sanitize the request and remove auth_token for redirection
289 # sanitize the request and remove auth_token for redirection
290 _req.GET.pop('auth_token')
290 _req.GET.pop('auth_token')
291 qs = _req.query_string
291 qs = _req.query_string
292 if qs:
292 if qs:
293 path += '?' + qs
293 path += '?' + qs
294
294
295 return path
295 return path
296
296
297
297
298 class CookieStoreWrapper(object):
298 class CookieStoreWrapper(object):
299
299
300 def __init__(self, cookie_store):
300 def __init__(self, cookie_store):
301 self.cookie_store = cookie_store
301 self.cookie_store = cookie_store
302
302
303 def __repr__(self):
303 def __repr__(self):
304 return 'CookieStore<%s>' % (self.cookie_store)
304 return 'CookieStore<%s>' % (self.cookie_store)
305
305
306 def get(self, key, other=None):
306 def get(self, key, other=None):
307 if isinstance(self.cookie_store, dict):
307 if isinstance(self.cookie_store, dict):
308 return self.cookie_store.get(key, other)
308 return self.cookie_store.get(key, other)
309 elif isinstance(self.cookie_store, AuthUser):
309 elif isinstance(self.cookie_store, AuthUser):
310 return self.cookie_store.__dict__.get(key, other)
310 return self.cookie_store.__dict__.get(key, other)
311
311
312
312
313 def _cached_perms_data(user_id, scope, user_is_admin,
313 def _cached_perms_data(user_id, scope, user_is_admin,
314 user_inherit_default_permissions, explicit, algo,
314 user_inherit_default_permissions, explicit, algo,
315 calculate_super_admin):
315 calculate_super_admin):
316
316
317 permissions = PermissionCalculator(
317 permissions = PermissionCalculator(
318 user_id, scope, user_is_admin, user_inherit_default_permissions,
318 user_id, scope, user_is_admin, user_inherit_default_permissions,
319 explicit, algo, calculate_super_admin)
319 explicit, algo, calculate_super_admin)
320 return permissions.calculate()
320 return permissions.calculate()
321
321
322
322
323 class PermOrigin(object):
323 class PermOrigin(object):
324 SUPER_ADMIN = 'superadmin'
324 SUPER_ADMIN = 'superadmin'
325
325
326 REPO_USER = 'user:%s'
326 REPO_USER = 'user:%s'
327 REPO_USERGROUP = 'usergroup:%s'
327 REPO_USERGROUP = 'usergroup:%s'
328 REPO_OWNER = 'repo.owner'
328 REPO_OWNER = 'repo.owner'
329 REPO_DEFAULT = 'repo.default'
329 REPO_DEFAULT = 'repo.default'
330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
331 REPO_PRIVATE = 'repo.private'
331 REPO_PRIVATE = 'repo.private'
332
332
333 REPOGROUP_USER = 'user:%s'
333 REPOGROUP_USER = 'user:%s'
334 REPOGROUP_USERGROUP = 'usergroup:%s'
334 REPOGROUP_USERGROUP = 'usergroup:%s'
335 REPOGROUP_OWNER = 'group.owner'
335 REPOGROUP_OWNER = 'group.owner'
336 REPOGROUP_DEFAULT = 'group.default'
336 REPOGROUP_DEFAULT = 'group.default'
337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
338
338
339 USERGROUP_USER = 'user:%s'
339 USERGROUP_USER = 'user:%s'
340 USERGROUP_USERGROUP = 'usergroup:%s'
340 USERGROUP_USERGROUP = 'usergroup:%s'
341 USERGROUP_OWNER = 'usergroup.owner'
341 USERGROUP_OWNER = 'usergroup.owner'
342 USERGROUP_DEFAULT = 'usergroup.default'
342 USERGROUP_DEFAULT = 'usergroup.default'
343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
344
344
345
345
346 class PermOriginDict(dict):
346 class PermOriginDict(dict):
347 """
347 """
348 A special dict used for tracking permissions along with their origins.
348 A special dict used for tracking permissions along with their origins.
349
349
350 `__setitem__` has been overridden to expect a tuple(perm, origin)
350 `__setitem__` has been overridden to expect a tuple(perm, origin)
351 `__getitem__` will return only the perm
351 `__getitem__` will return only the perm
352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
353
353
354 >>> perms = PermOriginDict()
354 >>> perms = PermOriginDict()
355 >>> perms['resource'] = 'read', 'default'
355 >>> perms['resource'] = 'read', 'default'
356 >>> perms['resource']
356 >>> perms['resource']
357 'read'
357 'read'
358 >>> perms['resource'] = 'write', 'admin'
358 >>> perms['resource'] = 'write', 'admin'
359 >>> perms['resource']
359 >>> perms['resource']
360 'write'
360 'write'
361 >>> perms.perm_origin_stack
361 >>> perms.perm_origin_stack
362 {'resource': [('read', 'default'), ('write', 'admin')]}
362 {'resource': [('read', 'default'), ('write', 'admin')]}
363 """
363 """
364
364
365 def __init__(self, *args, **kw):
365 def __init__(self, *args, **kw):
366 dict.__init__(self, *args, **kw)
366 dict.__init__(self, *args, **kw)
367 self.perm_origin_stack = collections.OrderedDict()
367 self.perm_origin_stack = collections.OrderedDict()
368
368
369 def __setitem__(self, key, (perm, origin)):
369 def __setitem__(self, key, (perm, origin)):
370 self.perm_origin_stack.setdefault(key, []).append(
370 self.perm_origin_stack.setdefault(key, []).append(
371 (perm, origin))
371 (perm, origin))
372 dict.__setitem__(self, key, perm)
372 dict.__setitem__(self, key, perm)
373
373
374
374
375 class BranchPermOriginDict(PermOriginDict):
375 class BranchPermOriginDict(PermOriginDict):
376 """
376 """
377 Dedicated branch permissions dict, with tracking of patterns and origins.
377 Dedicated branch permissions dict, with tracking of patterns and origins.
378
378
379 >>> perms = BranchPermOriginDict()
379 >>> perms = BranchPermOriginDict()
380 >>> perms['resource'] = '*pattern', 'read', 'default'
380 >>> perms['resource'] = '*pattern', 'read', 'default'
381 >>> perms['resource']
381 >>> perms['resource']
382 {'*pattern': 'read'}
382 {'*pattern': 'read'}
383 >>> perms['resource'] = '*pattern', 'write', 'admin'
383 >>> perms['resource'] = '*pattern', 'write', 'admin'
384 >>> perms['resource']
384 >>> perms['resource']
385 {'*pattern': 'write'}
385 {'*pattern': 'write'}
386 >>> perms.perm_origin_stack
386 >>> perms.perm_origin_stack
387 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
387 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
388 """
388 """
389 def __setitem__(self, key, (pattern, perm, origin)):
389 def __setitem__(self, key, (pattern, perm, origin)):
390
390
391 self.perm_origin_stack.setdefault(key, {}) \
391 self.perm_origin_stack.setdefault(key, {}) \
392 .setdefault(pattern, []).append((perm, origin))
392 .setdefault(pattern, []).append((perm, origin))
393
393
394 if key in self:
394 if key in self:
395 self[key].__setitem__(pattern, perm)
395 self[key].__setitem__(pattern, perm)
396 else:
396 else:
397 patterns = collections.OrderedDict()
397 patterns = collections.OrderedDict()
398 patterns[pattern] = perm
398 patterns[pattern] = perm
399 dict.__setitem__(self, key, patterns)
399 dict.__setitem__(self, key, patterns)
400
400
401
401
402 class PermissionCalculator(object):
402 class PermissionCalculator(object):
403
403
404 def __init__(
404 def __init__(
405 self, user_id, scope, user_is_admin,
405 self, user_id, scope, user_is_admin,
406 user_inherit_default_permissions, explicit, algo,
406 user_inherit_default_permissions, explicit, algo,
407 calculate_super_admin_as_user=False):
407 calculate_super_admin_as_user=False):
408
408
409 self.user_id = user_id
409 self.user_id = user_id
410 self.user_is_admin = user_is_admin
410 self.user_is_admin = user_is_admin
411 self.inherit_default_permissions = user_inherit_default_permissions
411 self.inherit_default_permissions = user_inherit_default_permissions
412 self.explicit = explicit
412 self.explicit = explicit
413 self.algo = algo
413 self.algo = algo
414 self.calculate_super_admin_as_user = calculate_super_admin_as_user
414 self.calculate_super_admin_as_user = calculate_super_admin_as_user
415
415
416 scope = scope or {}
416 scope = scope or {}
417 self.scope_repo_id = scope.get('repo_id')
417 self.scope_repo_id = scope.get('repo_id')
418 self.scope_repo_group_id = scope.get('repo_group_id')
418 self.scope_repo_group_id = scope.get('repo_group_id')
419 self.scope_user_group_id = scope.get('user_group_id')
419 self.scope_user_group_id = scope.get('user_group_id')
420
420
421 self.default_user_id = User.get_default_user(cache=True).user_id
421 self.default_user_id = User.get_default_user(cache=True).user_id
422
422
423 self.permissions_repositories = PermOriginDict()
423 self.permissions_repositories = PermOriginDict()
424 self.permissions_repository_groups = PermOriginDict()
424 self.permissions_repository_groups = PermOriginDict()
425 self.permissions_user_groups = PermOriginDict()
425 self.permissions_user_groups = PermOriginDict()
426 self.permissions_repository_branches = BranchPermOriginDict()
426 self.permissions_repository_branches = BranchPermOriginDict()
427 self.permissions_global = set()
427 self.permissions_global = set()
428
428
429 self.default_repo_perms = Permission.get_default_repo_perms(
429 self.default_repo_perms = Permission.get_default_repo_perms(
430 self.default_user_id, self.scope_repo_id)
430 self.default_user_id, self.scope_repo_id)
431 self.default_repo_groups_perms = Permission.get_default_group_perms(
431 self.default_repo_groups_perms = Permission.get_default_group_perms(
432 self.default_user_id, self.scope_repo_group_id)
432 self.default_user_id, self.scope_repo_group_id)
433 self.default_user_group_perms = \
433 self.default_user_group_perms = \
434 Permission.get_default_user_group_perms(
434 Permission.get_default_user_group_perms(
435 self.default_user_id, self.scope_user_group_id)
435 self.default_user_id, self.scope_user_group_id)
436
436
437 # default branch perms
437 # default branch perms
438 self.default_branch_repo_perms = \
438 self.default_branch_repo_perms = \
439 Permission.get_default_repo_branch_perms(
439 Permission.get_default_repo_branch_perms(
440 self.default_user_id, self.scope_repo_id)
440 self.default_user_id, self.scope_repo_id)
441
441
442 def calculate(self):
442 def calculate(self):
443 if self.user_is_admin and not self.calculate_super_admin_as_user:
443 if self.user_is_admin and not self.calculate_super_admin_as_user:
444 return self._calculate_admin_permissions()
444 return self._calculate_admin_permissions()
445
445
446 self._calculate_global_default_permissions()
446 self._calculate_global_default_permissions()
447 self._calculate_global_permissions()
447 self._calculate_global_permissions()
448 self._calculate_default_permissions()
448 self._calculate_default_permissions()
449 self._calculate_repository_permissions()
449 self._calculate_repository_permissions()
450 self._calculate_repository_branch_permissions()
450 self._calculate_repository_branch_permissions()
451 self._calculate_repository_group_permissions()
451 self._calculate_repository_group_permissions()
452 self._calculate_user_group_permissions()
452 self._calculate_user_group_permissions()
453 return self._permission_structure()
453 return self._permission_structure()
454
454
455 def _calculate_admin_permissions(self):
455 def _calculate_admin_permissions(self):
456 """
456 """
457 admin user have all default rights for repositories
457 admin user have all default rights for repositories
458 and groups set to admin
458 and groups set to admin
459 """
459 """
460 self.permissions_global.add('hg.admin')
460 self.permissions_global.add('hg.admin')
461 self.permissions_global.add('hg.create.write_on_repogroup.true')
461 self.permissions_global.add('hg.create.write_on_repogroup.true')
462
462
463 # repositories
463 # repositories
464 for perm in self.default_repo_perms:
464 for perm in self.default_repo_perms:
465 r_k = perm.UserRepoToPerm.repository.repo_name
465 r_k = perm.UserRepoToPerm.repository.repo_name
466 p = 'repository.admin'
466 p = 'repository.admin'
467 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
467 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
468
468
469 # repository groups
469 # repository groups
470 for perm in self.default_repo_groups_perms:
470 for perm in self.default_repo_groups_perms:
471 rg_k = perm.UserRepoGroupToPerm.group.group_name
471 rg_k = perm.UserRepoGroupToPerm.group.group_name
472 p = 'group.admin'
472 p = 'group.admin'
473 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
473 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
474
474
475 # user groups
475 # user groups
476 for perm in self.default_user_group_perms:
476 for perm in self.default_user_group_perms:
477 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
477 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
478 p = 'usergroup.admin'
478 p = 'usergroup.admin'
479 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN
479 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN
480
480
481 # branch permissions
481 # branch permissions
482 # since super-admin also can have custom rule permissions
482 # since super-admin also can have custom rule permissions
483 # we *always* need to calculate those inherited from default, and also explicit
483 # we *always* need to calculate those inherited from default, and also explicit
484 self._calculate_default_permissions_repository_branches(
484 self._calculate_default_permissions_repository_branches(
485 user_inherit_object_permissions=False)
485 user_inherit_object_permissions=False)
486 self._calculate_repository_branch_permissions()
486 self._calculate_repository_branch_permissions()
487
487
488 return self._permission_structure()
488 return self._permission_structure()
489
489
490 def _calculate_global_default_permissions(self):
490 def _calculate_global_default_permissions(self):
491 """
491 """
492 global permissions taken from the default user
492 global permissions taken from the default user
493 """
493 """
494 default_global_perms = UserToPerm.query()\
494 default_global_perms = UserToPerm.query()\
495 .filter(UserToPerm.user_id == self.default_user_id)\
495 .filter(UserToPerm.user_id == self.default_user_id)\
496 .options(joinedload(UserToPerm.permission))
496 .options(joinedload(UserToPerm.permission))
497
497
498 for perm in default_global_perms:
498 for perm in default_global_perms:
499 self.permissions_global.add(perm.permission.permission_name)
499 self.permissions_global.add(perm.permission.permission_name)
500
500
501 if self.user_is_admin:
501 if self.user_is_admin:
502 self.permissions_global.add('hg.admin')
502 self.permissions_global.add('hg.admin')
503 self.permissions_global.add('hg.create.write_on_repogroup.true')
503 self.permissions_global.add('hg.create.write_on_repogroup.true')
504
504
505 def _calculate_global_permissions(self):
505 def _calculate_global_permissions(self):
506 """
506 """
507 Set global system permissions with user permissions or permissions
507 Set global system permissions with user permissions or permissions
508 taken from the user groups of the current user.
508 taken from the user groups of the current user.
509
509
510 The permissions include repo creating, repo group creating, forking
510 The permissions include repo creating, repo group creating, forking
511 etc.
511 etc.
512 """
512 """
513
513
514 # now we read the defined permissions and overwrite what we have set
514 # now we read the defined permissions and overwrite what we have set
515 # before those can be configured from groups or users explicitly.
515 # before those can be configured from groups or users explicitly.
516
516
517 # In case we want to extend this list we should make sure
517 # In case we want to extend this list we should make sure
518 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
518 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
519 _configurable = frozenset([
519 _configurable = frozenset([
520 'hg.fork.none', 'hg.fork.repository',
520 'hg.fork.none', 'hg.fork.repository',
521 'hg.create.none', 'hg.create.repository',
521 'hg.create.none', 'hg.create.repository',
522 'hg.usergroup.create.false', 'hg.usergroup.create.true',
522 'hg.usergroup.create.false', 'hg.usergroup.create.true',
523 'hg.repogroup.create.false', 'hg.repogroup.create.true',
523 'hg.repogroup.create.false', 'hg.repogroup.create.true',
524 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
524 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
525 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
525 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
526 ])
526 ])
527
527
528 # USER GROUPS comes first user group global permissions
528 # USER GROUPS comes first user group global permissions
529 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
529 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
530 .options(joinedload(UserGroupToPerm.permission))\
530 .options(joinedload(UserGroupToPerm.permission))\
531 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
531 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
532 UserGroupMember.users_group_id))\
532 UserGroupMember.users_group_id))\
533 .filter(UserGroupMember.user_id == self.user_id)\
533 .filter(UserGroupMember.user_id == self.user_id)\
534 .order_by(UserGroupToPerm.users_group_id)\
534 .order_by(UserGroupToPerm.users_group_id)\
535 .all()
535 .all()
536
536
537 # need to group here by groups since user can be in more than
537 # need to group here by groups since user can be in more than
538 # one group, so we get all groups
538 # one group, so we get all groups
539 _explicit_grouped_perms = [
539 _explicit_grouped_perms = [
540 [x, list(y)] for x, y in
540 [x, list(y)] for x, y in
541 itertools.groupby(user_perms_from_users_groups,
541 itertools.groupby(user_perms_from_users_groups,
542 lambda _x: _x.users_group)]
542 lambda _x: _x.users_group)]
543
543
544 for gr, perms in _explicit_grouped_perms:
544 for gr, perms in _explicit_grouped_perms:
545 # since user can be in multiple groups iterate over them and
545 # since user can be in multiple groups iterate over them and
546 # select the lowest permissions first (more explicit)
546 # select the lowest permissions first (more explicit)
547 # TODO(marcink): do this^^
547 # TODO(marcink): do this^^
548
548
549 # group doesn't inherit default permissions so we actually set them
549 # group doesn't inherit default permissions so we actually set them
550 if not gr.inherit_default_permissions:
550 if not gr.inherit_default_permissions:
551 # NEED TO IGNORE all previously set configurable permissions
551 # NEED TO IGNORE all previously set configurable permissions
552 # and replace them with explicitly set from this user
552 # and replace them with explicitly set from this user
553 # group permissions
553 # group permissions
554 self.permissions_global = self.permissions_global.difference(
554 self.permissions_global = self.permissions_global.difference(
555 _configurable)
555 _configurable)
556 for perm in perms:
556 for perm in perms:
557 self.permissions_global.add(perm.permission.permission_name)
557 self.permissions_global.add(perm.permission.permission_name)
558
558
559 # user explicit global permissions
559 # user explicit global permissions
560 user_perms = Session().query(UserToPerm)\
560 user_perms = Session().query(UserToPerm)\
561 .options(joinedload(UserToPerm.permission))\
561 .options(joinedload(UserToPerm.permission))\
562 .filter(UserToPerm.user_id == self.user_id).all()
562 .filter(UserToPerm.user_id == self.user_id).all()
563
563
564 if not self.inherit_default_permissions:
564 if not self.inherit_default_permissions:
565 # NEED TO IGNORE all configurable permissions and
565 # NEED TO IGNORE all configurable permissions and
566 # replace them with explicitly set from this user permissions
566 # replace them with explicitly set from this user permissions
567 self.permissions_global = self.permissions_global.difference(
567 self.permissions_global = self.permissions_global.difference(
568 _configurable)
568 _configurable)
569 for perm in user_perms:
569 for perm in user_perms:
570 self.permissions_global.add(perm.permission.permission_name)
570 self.permissions_global.add(perm.permission.permission_name)
571
571
572 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
572 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
573 for perm in self.default_repo_perms:
573 for perm in self.default_repo_perms:
574 r_k = perm.UserRepoToPerm.repository.repo_name
574 r_k = perm.UserRepoToPerm.repository.repo_name
575 p = perm.Permission.permission_name
575 p = perm.Permission.permission_name
576 o = PermOrigin.REPO_DEFAULT
576 o = PermOrigin.REPO_DEFAULT
577 self.permissions_repositories[r_k] = p, o
577 self.permissions_repositories[r_k] = p, o
578
578
579 # if we decide this user isn't inheriting permissions from
579 # if we decide this user isn't inheriting permissions from
580 # default user we set him to .none so only explicit
580 # default user we set him to .none so only explicit
581 # permissions work
581 # permissions work
582 if not user_inherit_object_permissions:
582 if not user_inherit_object_permissions:
583 p = 'repository.none'
583 p = 'repository.none'
584 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
584 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
585 self.permissions_repositories[r_k] = p, o
585 self.permissions_repositories[r_k] = p, o
586
586
587 if perm.Repository.private and not (
587 if perm.Repository.private and not (
588 perm.Repository.user_id == self.user_id):
588 perm.Repository.user_id == self.user_id):
589 # disable defaults for private repos,
589 # disable defaults for private repos,
590 p = 'repository.none'
590 p = 'repository.none'
591 o = PermOrigin.REPO_PRIVATE
591 o = PermOrigin.REPO_PRIVATE
592 self.permissions_repositories[r_k] = p, o
592 self.permissions_repositories[r_k] = p, o
593
593
594 elif perm.Repository.user_id == self.user_id:
594 elif perm.Repository.user_id == self.user_id:
595 # set admin if owner
595 # set admin if owner
596 p = 'repository.admin'
596 p = 'repository.admin'
597 o = PermOrigin.REPO_OWNER
597 o = PermOrigin.REPO_OWNER
598 self.permissions_repositories[r_k] = p, o
598 self.permissions_repositories[r_k] = p, o
599
599
600 if self.user_is_admin:
600 if self.user_is_admin:
601 p = 'repository.admin'
601 p = 'repository.admin'
602 o = PermOrigin.SUPER_ADMIN
602 o = PermOrigin.SUPER_ADMIN
603 self.permissions_repositories[r_k] = p, o
603 self.permissions_repositories[r_k] = p, o
604
604
605 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
605 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
606 for perm in self.default_branch_repo_perms:
606 for perm in self.default_branch_repo_perms:
607
607
608 r_k = perm.UserRepoToPerm.repository.repo_name
608 r_k = perm.UserRepoToPerm.repository.repo_name
609 p = perm.Permission.permission_name
609 p = perm.Permission.permission_name
610 pattern = perm.UserToRepoBranchPermission.branch_pattern
610 pattern = perm.UserToRepoBranchPermission.branch_pattern
611 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
611 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
612
612
613 if not self.explicit:
613 if not self.explicit:
614 # TODO(marcink): fix this for multiple entries
614 # TODO(marcink): fix this for multiple entries
615 cur_perm = self.permissions_repository_branches.get(r_k) or 'branch.none'
615 cur_perm = self.permissions_repository_branches.get(r_k) or 'branch.none'
616 p = self._choose_permission(p, cur_perm)
616 p = self._choose_permission(p, cur_perm)
617
617
618 # NOTE(marcink): register all pattern/perm instances in this
618 # NOTE(marcink): register all pattern/perm instances in this
619 # special dict that aggregates entries
619 # special dict that aggregates entries
620 self.permissions_repository_branches[r_k] = pattern, p, o
620 self.permissions_repository_branches[r_k] = pattern, p, o
621
621
622 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
622 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
623 for perm in self.default_repo_groups_perms:
623 for perm in self.default_repo_groups_perms:
624 rg_k = perm.UserRepoGroupToPerm.group.group_name
624 rg_k = perm.UserRepoGroupToPerm.group.group_name
625 p = perm.Permission.permission_name
625 p = perm.Permission.permission_name
626 o = PermOrigin.REPOGROUP_DEFAULT
626 o = PermOrigin.REPOGROUP_DEFAULT
627 self.permissions_repository_groups[rg_k] = p, o
627 self.permissions_repository_groups[rg_k] = p, o
628
628
629 # if we decide this user isn't inheriting permissions from default
629 # if we decide this user isn't inheriting permissions from default
630 # user we set him to .none so only explicit permissions work
630 # user we set him to .none so only explicit permissions work
631 if not user_inherit_object_permissions:
631 if not user_inherit_object_permissions:
632 p = 'group.none'
632 p = 'group.none'
633 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
633 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
634 self.permissions_repository_groups[rg_k] = p, o
634 self.permissions_repository_groups[rg_k] = p, o
635
635
636 if perm.RepoGroup.user_id == self.user_id:
636 if perm.RepoGroup.user_id == self.user_id:
637 # set admin if owner
637 # set admin if owner
638 p = 'group.admin'
638 p = 'group.admin'
639 o = PermOrigin.REPOGROUP_OWNER
639 o = PermOrigin.REPOGROUP_OWNER
640 self.permissions_repository_groups[rg_k] = p, o
640 self.permissions_repository_groups[rg_k] = p, o
641
641
642 if self.user_is_admin:
642 if self.user_is_admin:
643 p = 'group.admin'
643 p = 'group.admin'
644 o = PermOrigin.SUPER_ADMIN
644 o = PermOrigin.SUPER_ADMIN
645 self.permissions_repository_groups[rg_k] = p, o
645 self.permissions_repository_groups[rg_k] = p, o
646
646
647 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
647 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
648 for perm in self.default_user_group_perms:
648 for perm in self.default_user_group_perms:
649 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
649 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
650 p = perm.Permission.permission_name
650 p = perm.Permission.permission_name
651 o = PermOrigin.USERGROUP_DEFAULT
651 o = PermOrigin.USERGROUP_DEFAULT
652 self.permissions_user_groups[u_k] = p, o
652 self.permissions_user_groups[u_k] = p, o
653
653
654 # if we decide this user isn't inheriting permissions from default
654 # if we decide this user isn't inheriting permissions from default
655 # user we set him to .none so only explicit permissions work
655 # user we set him to .none so only explicit permissions work
656 if not user_inherit_object_permissions:
656 if not user_inherit_object_permissions:
657 p = 'usergroup.none'
657 p = 'usergroup.none'
658 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
658 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
659 self.permissions_user_groups[u_k] = p, o
659 self.permissions_user_groups[u_k] = p, o
660
660
661 if perm.UserGroup.user_id == self.user_id:
661 if perm.UserGroup.user_id == self.user_id:
662 # set admin if owner
662 # set admin if owner
663 p = 'usergroup.admin'
663 p = 'usergroup.admin'
664 o = PermOrigin.USERGROUP_OWNER
664 o = PermOrigin.USERGROUP_OWNER
665 self.permissions_user_groups[u_k] = p, o
665 self.permissions_user_groups[u_k] = p, o
666
666
667 if self.user_is_admin:
667 if self.user_is_admin:
668 p = 'usergroup.admin'
668 p = 'usergroup.admin'
669 o = PermOrigin.SUPER_ADMIN
669 o = PermOrigin.SUPER_ADMIN
670 self.permissions_user_groups[u_k] = p, o
670 self.permissions_user_groups[u_k] = p, o
671
671
672 def _calculate_default_permissions(self):
672 def _calculate_default_permissions(self):
673 """
673 """
674 Set default user permissions for repositories, repository branches,
674 Set default user permissions for repositories, repository branches,
675 repository groups, user groups taken from the default user.
675 repository groups, user groups taken from the default user.
676
676
677 Calculate inheritance of object permissions based on what we have now
677 Calculate inheritance of object permissions based on what we have now
678 in GLOBAL permissions. We check if .false is in GLOBAL since this is
678 in GLOBAL permissions. We check if .false is in GLOBAL since this is
679 explicitly set. Inherit is the opposite of .false being there.
679 explicitly set. Inherit is the opposite of .false being there.
680
680
681 .. note::
681 .. note::
682
682
683 the syntax is little bit odd but what we need to check here is
683 the syntax is little bit odd but what we need to check here is
684 the opposite of .false permission being in the list so even for
684 the opposite of .false permission being in the list so even for
685 inconsistent state when both .true/.false is there
685 inconsistent state when both .true/.false is there
686 .false is more important
686 .false is more important
687
687
688 """
688 """
689 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
689 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
690 in self.permissions_global)
690 in self.permissions_global)
691
691
692 # default permissions inherited from `default` user permissions
692 # default permissions inherited from `default` user permissions
693 self._calculate_default_permissions_repositories(
693 self._calculate_default_permissions_repositories(
694 user_inherit_object_permissions)
694 user_inherit_object_permissions)
695
695
696 self._calculate_default_permissions_repository_branches(
696 self._calculate_default_permissions_repository_branches(
697 user_inherit_object_permissions)
697 user_inherit_object_permissions)
698
698
699 self._calculate_default_permissions_repository_groups(
699 self._calculate_default_permissions_repository_groups(
700 user_inherit_object_permissions)
700 user_inherit_object_permissions)
701
701
702 self._calculate_default_permissions_user_groups(
702 self._calculate_default_permissions_user_groups(
703 user_inherit_object_permissions)
703 user_inherit_object_permissions)
704
704
705 def _calculate_repository_permissions(self):
705 def _calculate_repository_permissions(self):
706 """
706 """
707 Repository permissions for the current user.
707 Repository permissions for the current user.
708
708
709 Check if the user is part of user groups for this repository and
709 Check if the user is part of user groups for this repository and
710 fill in the permission from it. `_choose_permission` decides of which
710 fill in the permission from it. `_choose_permission` decides of which
711 permission should be selected based on selected method.
711 permission should be selected based on selected method.
712 """
712 """
713
713
714 # user group for repositories permissions
714 # user group for repositories permissions
715 user_repo_perms_from_user_group = Permission\
715 user_repo_perms_from_user_group = Permission\
716 .get_default_repo_perms_from_user_group(
716 .get_default_repo_perms_from_user_group(
717 self.user_id, self.scope_repo_id)
717 self.user_id, self.scope_repo_id)
718
718
719 multiple_counter = collections.defaultdict(int)
719 multiple_counter = collections.defaultdict(int)
720 for perm in user_repo_perms_from_user_group:
720 for perm in user_repo_perms_from_user_group:
721 r_k = perm.UserGroupRepoToPerm.repository.repo_name
721 r_k = perm.UserGroupRepoToPerm.repository.repo_name
722 multiple_counter[r_k] += 1
722 multiple_counter[r_k] += 1
723 p = perm.Permission.permission_name
723 p = perm.Permission.permission_name
724 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
724 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
725 .users_group.users_group_name
725 .users_group.users_group_name
726
726
727 if multiple_counter[r_k] > 1:
727 if multiple_counter[r_k] > 1:
728 cur_perm = self.permissions_repositories[r_k]
728 cur_perm = self.permissions_repositories[r_k]
729 p = self._choose_permission(p, cur_perm)
729 p = self._choose_permission(p, cur_perm)
730
730
731 self.permissions_repositories[r_k] = p, o
731 self.permissions_repositories[r_k] = p, o
732
732
733 if perm.Repository.user_id == self.user_id:
733 if perm.Repository.user_id == self.user_id:
734 # set admin if owner
734 # set admin if owner
735 p = 'repository.admin'
735 p = 'repository.admin'
736 o = PermOrigin.REPO_OWNER
736 o = PermOrigin.REPO_OWNER
737 self.permissions_repositories[r_k] = p, o
737 self.permissions_repositories[r_k] = p, o
738
738
739 if self.user_is_admin:
739 if self.user_is_admin:
740 p = 'repository.admin'
740 p = 'repository.admin'
741 o = PermOrigin.SUPER_ADMIN
741 o = PermOrigin.SUPER_ADMIN
742 self.permissions_repositories[r_k] = p, o
742 self.permissions_repositories[r_k] = p, o
743
743
744 # user explicit permissions for repositories, overrides any specified
744 # user explicit permissions for repositories, overrides any specified
745 # by the group permission
745 # by the group permission
746 user_repo_perms = Permission.get_default_repo_perms(
746 user_repo_perms = Permission.get_default_repo_perms(
747 self.user_id, self.scope_repo_id)
747 self.user_id, self.scope_repo_id)
748 for perm in user_repo_perms:
748 for perm in user_repo_perms:
749 r_k = perm.UserRepoToPerm.repository.repo_name
749 r_k = perm.UserRepoToPerm.repository.repo_name
750 p = perm.Permission.permission_name
750 p = perm.Permission.permission_name
751 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
751 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
752
752
753 if not self.explicit:
753 if not self.explicit:
754 cur_perm = self.permissions_repositories.get(
754 cur_perm = self.permissions_repositories.get(
755 r_k, 'repository.none')
755 r_k, 'repository.none')
756 p = self._choose_permission(p, cur_perm)
756 p = self._choose_permission(p, cur_perm)
757
757
758 self.permissions_repositories[r_k] = p, o
758 self.permissions_repositories[r_k] = p, o
759
759
760 if perm.Repository.user_id == self.user_id:
760 if perm.Repository.user_id == self.user_id:
761 # set admin if owner
761 # set admin if owner
762 p = 'repository.admin'
762 p = 'repository.admin'
763 o = PermOrigin.REPO_OWNER
763 o = PermOrigin.REPO_OWNER
764 self.permissions_repositories[r_k] = p, o
764 self.permissions_repositories[r_k] = p, o
765
765
766 if self.user_is_admin:
766 if self.user_is_admin:
767 p = 'repository.admin'
767 p = 'repository.admin'
768 o = PermOrigin.SUPER_ADMIN
768 o = PermOrigin.SUPER_ADMIN
769 self.permissions_repositories[r_k] = p, o
769 self.permissions_repositories[r_k] = p, o
770
770
771 def _calculate_repository_branch_permissions(self):
771 def _calculate_repository_branch_permissions(self):
772 # user group for repositories permissions
772 # user group for repositories permissions
773 user_repo_branch_perms_from_user_group = Permission\
773 user_repo_branch_perms_from_user_group = Permission\
774 .get_default_repo_branch_perms_from_user_group(
774 .get_default_repo_branch_perms_from_user_group(
775 self.user_id, self.scope_repo_id)
775 self.user_id, self.scope_repo_id)
776
776
777 multiple_counter = collections.defaultdict(int)
777 multiple_counter = collections.defaultdict(int)
778 for perm in user_repo_branch_perms_from_user_group:
778 for perm in user_repo_branch_perms_from_user_group:
779 r_k = perm.UserGroupRepoToPerm.repository.repo_name
779 r_k = perm.UserGroupRepoToPerm.repository.repo_name
780 p = perm.Permission.permission_name
780 p = perm.Permission.permission_name
781 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
781 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
782 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
782 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
783 .users_group.users_group_name
783 .users_group.users_group_name
784
784
785 multiple_counter[r_k] += 1
785 multiple_counter[r_k] += 1
786 if multiple_counter[r_k] > 1:
786 if multiple_counter[r_k] > 1:
787 # TODO(marcink): fix this for multi branch support, and multiple entries
787 # TODO(marcink): fix this for multi branch support, and multiple entries
788 cur_perm = self.permissions_repository_branches[r_k]
788 cur_perm = self.permissions_repository_branches[r_k]
789 p = self._choose_permission(p, cur_perm)
789 p = self._choose_permission(p, cur_perm)
790
790
791 self.permissions_repository_branches[r_k] = pattern, p, o
791 self.permissions_repository_branches[r_k] = pattern, p, o
792
792
793 # user explicit branch permissions for repositories, overrides
793 # user explicit branch permissions for repositories, overrides
794 # any specified by the group permission
794 # any specified by the group permission
795 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
795 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
796 self.user_id, self.scope_repo_id)
796 self.user_id, self.scope_repo_id)
797
797
798 for perm in user_repo_branch_perms:
798 for perm in user_repo_branch_perms:
799
799
800 r_k = perm.UserRepoToPerm.repository.repo_name
800 r_k = perm.UserRepoToPerm.repository.repo_name
801 p = perm.Permission.permission_name
801 p = perm.Permission.permission_name
802 pattern = perm.UserToRepoBranchPermission.branch_pattern
802 pattern = perm.UserToRepoBranchPermission.branch_pattern
803 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
803 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
804
804
805 if not self.explicit:
805 if not self.explicit:
806 # TODO(marcink): fix this for multiple entries
806 # TODO(marcink): fix this for multiple entries
807 cur_perm = self.permissions_repository_branches.get(r_k) or 'branch.none'
807 cur_perm = self.permissions_repository_branches.get(r_k) or 'branch.none'
808 p = self._choose_permission(p, cur_perm)
808 p = self._choose_permission(p, cur_perm)
809
809
810 # NOTE(marcink): register all pattern/perm instances in this
810 # NOTE(marcink): register all pattern/perm instances in this
811 # special dict that aggregates entries
811 # special dict that aggregates entries
812 self.permissions_repository_branches[r_k] = pattern, p, o
812 self.permissions_repository_branches[r_k] = pattern, p, o
813
813
814 def _calculate_repository_group_permissions(self):
814 def _calculate_repository_group_permissions(self):
815 """
815 """
816 Repository group permissions for the current user.
816 Repository group permissions for the current user.
817
817
818 Check if the user is part of user groups for repository groups and
818 Check if the user is part of user groups for repository groups and
819 fill in the permissions from it. `_choose_permission` decides of which
819 fill in the permissions from it. `_choose_permission` decides of which
820 permission should be selected based on selected method.
820 permission should be selected based on selected method.
821 """
821 """
822 # user group for repo groups permissions
822 # user group for repo groups permissions
823 user_repo_group_perms_from_user_group = Permission\
823 user_repo_group_perms_from_user_group = Permission\
824 .get_default_group_perms_from_user_group(
824 .get_default_group_perms_from_user_group(
825 self.user_id, self.scope_repo_group_id)
825 self.user_id, self.scope_repo_group_id)
826
826
827 multiple_counter = collections.defaultdict(int)
827 multiple_counter = collections.defaultdict(int)
828 for perm in user_repo_group_perms_from_user_group:
828 for perm in user_repo_group_perms_from_user_group:
829 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
829 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
830 multiple_counter[rg_k] += 1
830 multiple_counter[rg_k] += 1
831 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
831 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
832 .users_group.users_group_name
832 .users_group.users_group_name
833 p = perm.Permission.permission_name
833 p = perm.Permission.permission_name
834
834
835 if multiple_counter[rg_k] > 1:
835 if multiple_counter[rg_k] > 1:
836 cur_perm = self.permissions_repository_groups[rg_k]
836 cur_perm = self.permissions_repository_groups[rg_k]
837 p = self._choose_permission(p, cur_perm)
837 p = self._choose_permission(p, cur_perm)
838 self.permissions_repository_groups[rg_k] = p, o
838 self.permissions_repository_groups[rg_k] = p, o
839
839
840 if perm.RepoGroup.user_id == self.user_id:
840 if perm.RepoGroup.user_id == self.user_id:
841 # set admin if owner, even for member of other user group
841 # set admin if owner, even for member of other user group
842 p = 'group.admin'
842 p = 'group.admin'
843 o = PermOrigin.REPOGROUP_OWNER
843 o = PermOrigin.REPOGROUP_OWNER
844 self.permissions_repository_groups[rg_k] = p, o
844 self.permissions_repository_groups[rg_k] = p, o
845
845
846 if self.user_is_admin:
846 if self.user_is_admin:
847 p = 'group.admin'
847 p = 'group.admin'
848 o = PermOrigin.SUPER_ADMIN
848 o = PermOrigin.SUPER_ADMIN
849 self.permissions_repository_groups[rg_k] = p, o
849 self.permissions_repository_groups[rg_k] = p, o
850
850
851 # user explicit permissions for repository groups
851 # user explicit permissions for repository groups
852 user_repo_groups_perms = Permission.get_default_group_perms(
852 user_repo_groups_perms = Permission.get_default_group_perms(
853 self.user_id, self.scope_repo_group_id)
853 self.user_id, self.scope_repo_group_id)
854 for perm in user_repo_groups_perms:
854 for perm in user_repo_groups_perms:
855 rg_k = perm.UserRepoGroupToPerm.group.group_name
855 rg_k = perm.UserRepoGroupToPerm.group.group_name
856 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
856 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
857 .user.username
857 .user.username
858 p = perm.Permission.permission_name
858 p = perm.Permission.permission_name
859
859
860 if not self.explicit:
860 if not self.explicit:
861 cur_perm = self.permissions_repository_groups.get(
861 cur_perm = self.permissions_repository_groups.get(
862 rg_k, 'group.none')
862 rg_k, 'group.none')
863 p = self._choose_permission(p, cur_perm)
863 p = self._choose_permission(p, cur_perm)
864
864
865 self.permissions_repository_groups[rg_k] = p, o
865 self.permissions_repository_groups[rg_k] = p, o
866
866
867 if perm.RepoGroup.user_id == self.user_id:
867 if perm.RepoGroup.user_id == self.user_id:
868 # set admin if owner
868 # set admin if owner
869 p = 'group.admin'
869 p = 'group.admin'
870 o = PermOrigin.REPOGROUP_OWNER
870 o = PermOrigin.REPOGROUP_OWNER
871 self.permissions_repository_groups[rg_k] = p, o
871 self.permissions_repository_groups[rg_k] = p, o
872
872
873 if self.user_is_admin:
873 if self.user_is_admin:
874 p = 'group.admin'
874 p = 'group.admin'
875 o = PermOrigin.SUPER_ADMIN
875 o = PermOrigin.SUPER_ADMIN
876 self.permissions_repository_groups[rg_k] = p, o
876 self.permissions_repository_groups[rg_k] = p, o
877
877
878 def _calculate_user_group_permissions(self):
878 def _calculate_user_group_permissions(self):
879 """
879 """
880 User group permissions for the current user.
880 User group permissions for the current user.
881 """
881 """
882 # user group for user group permissions
882 # user group for user group permissions
883 user_group_from_user_group = Permission\
883 user_group_from_user_group = Permission\
884 .get_default_user_group_perms_from_user_group(
884 .get_default_user_group_perms_from_user_group(
885 self.user_id, self.scope_user_group_id)
885 self.user_id, self.scope_user_group_id)
886
886
887 multiple_counter = collections.defaultdict(int)
887 multiple_counter = collections.defaultdict(int)
888 for perm in user_group_from_user_group:
888 for perm in user_group_from_user_group:
889 ug_k = perm.UserGroupUserGroupToPerm\
889 ug_k = perm.UserGroupUserGroupToPerm\
890 .target_user_group.users_group_name
890 .target_user_group.users_group_name
891 multiple_counter[ug_k] += 1
891 multiple_counter[ug_k] += 1
892 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
892 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
893 .user_group.users_group_name
893 .user_group.users_group_name
894 p = perm.Permission.permission_name
894 p = perm.Permission.permission_name
895
895
896 if multiple_counter[ug_k] > 1:
896 if multiple_counter[ug_k] > 1:
897 cur_perm = self.permissions_user_groups[ug_k]
897 cur_perm = self.permissions_user_groups[ug_k]
898 p = self._choose_permission(p, cur_perm)
898 p = self._choose_permission(p, cur_perm)
899
899
900 self.permissions_user_groups[ug_k] = p, o
900 self.permissions_user_groups[ug_k] = p, o
901
901
902 if perm.UserGroup.user_id == self.user_id:
902 if perm.UserGroup.user_id == self.user_id:
903 # set admin if owner, even for member of other user group
903 # set admin if owner, even for member of other user group
904 p = 'usergroup.admin'
904 p = 'usergroup.admin'
905 o = PermOrigin.USERGROUP_OWNER
905 o = PermOrigin.USERGROUP_OWNER
906 self.permissions_user_groups[ug_k] = p, o
906 self.permissions_user_groups[ug_k] = p, o
907
907
908 if self.user_is_admin:
908 if self.user_is_admin:
909 p = 'usergroup.admin'
909 p = 'usergroup.admin'
910 o = PermOrigin.SUPER_ADMIN
910 o = PermOrigin.SUPER_ADMIN
911 self.permissions_user_groups[ug_k] = p, o
911 self.permissions_user_groups[ug_k] = p, o
912
912
913 # user explicit permission for user groups
913 # user explicit permission for user groups
914 user_user_groups_perms = Permission.get_default_user_group_perms(
914 user_user_groups_perms = Permission.get_default_user_group_perms(
915 self.user_id, self.scope_user_group_id)
915 self.user_id, self.scope_user_group_id)
916 for perm in user_user_groups_perms:
916 for perm in user_user_groups_perms:
917 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
917 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
918 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
918 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
919 .user.username
919 .user.username
920 p = perm.Permission.permission_name
920 p = perm.Permission.permission_name
921
921
922 if not self.explicit:
922 if not self.explicit:
923 cur_perm = self.permissions_user_groups.get(
923 cur_perm = self.permissions_user_groups.get(
924 ug_k, 'usergroup.none')
924 ug_k, 'usergroup.none')
925 p = self._choose_permission(p, cur_perm)
925 p = self._choose_permission(p, cur_perm)
926
926
927 self.permissions_user_groups[ug_k] = p, o
927 self.permissions_user_groups[ug_k] = p, o
928
928
929 if perm.UserGroup.user_id == self.user_id:
929 if perm.UserGroup.user_id == self.user_id:
930 # set admin if owner
930 # set admin if owner
931 p = 'usergroup.admin'
931 p = 'usergroup.admin'
932 o = PermOrigin.USERGROUP_OWNER
932 o = PermOrigin.USERGROUP_OWNER
933 self.permissions_user_groups[ug_k] = p, o
933 self.permissions_user_groups[ug_k] = p, o
934
934
935 if self.user_is_admin:
935 if self.user_is_admin:
936 p = 'usergroup.admin'
936 p = 'usergroup.admin'
937 o = PermOrigin.SUPER_ADMIN
937 o = PermOrigin.SUPER_ADMIN
938 self.permissions_user_groups[ug_k] = p, o
938 self.permissions_user_groups[ug_k] = p, o
939
939
940 def _choose_permission(self, new_perm, cur_perm):
940 def _choose_permission(self, new_perm, cur_perm):
941 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
941 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
942 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
942 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
943 if self.algo == 'higherwin':
943 if self.algo == 'higherwin':
944 if new_perm_val > cur_perm_val:
944 if new_perm_val > cur_perm_val:
945 return new_perm
945 return new_perm
946 return cur_perm
946 return cur_perm
947 elif self.algo == 'lowerwin':
947 elif self.algo == 'lowerwin':
948 if new_perm_val < cur_perm_val:
948 if new_perm_val < cur_perm_val:
949 return new_perm
949 return new_perm
950 return cur_perm
950 return cur_perm
951
951
952 def _permission_structure(self):
952 def _permission_structure(self):
953 return {
953 return {
954 'global': self.permissions_global,
954 'global': self.permissions_global,
955 'repositories': self.permissions_repositories,
955 'repositories': self.permissions_repositories,
956 'repository_branches': self.permissions_repository_branches,
956 'repository_branches': self.permissions_repository_branches,
957 'repositories_groups': self.permissions_repository_groups,
957 'repositories_groups': self.permissions_repository_groups,
958 'user_groups': self.permissions_user_groups,
958 'user_groups': self.permissions_user_groups,
959 }
959 }
960
960
961
961
962 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
962 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
963 """
963 """
964 Check if given controller_name is in whitelist of auth token access
964 Check if given controller_name is in whitelist of auth token access
965 """
965 """
966 if not whitelist:
966 if not whitelist:
967 from rhodecode import CONFIG
967 from rhodecode import CONFIG
968 whitelist = aslist(
968 whitelist = aslist(
969 CONFIG.get('api_access_controllers_whitelist'), sep=',')
969 CONFIG.get('api_access_controllers_whitelist'), sep=',')
970 # backward compat translation
970 # backward compat translation
971 compat = {
971 compat = {
972 # old controller, new VIEW
972 # old controller, new VIEW
973 'ChangesetController:*': 'RepoCommitsView:*',
973 'ChangesetController:*': 'RepoCommitsView:*',
974 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
974 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
975 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
975 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
976 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
976 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
977 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
977 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
978 'GistsController:*': 'GistView:*',
978 'GistsController:*': 'GistView:*',
979 }
979 }
980
980
981 log.debug(
981 log.debug(
982 'Allowed views for AUTH TOKEN access: %s' % (whitelist,))
982 'Allowed views for AUTH TOKEN access: %s' % (whitelist,))
983 auth_token_access_valid = False
983 auth_token_access_valid = False
984
984
985 for entry in whitelist:
985 for entry in whitelist:
986 token_match = True
986 token_match = True
987 if entry in compat:
987 if entry in compat:
988 # translate from old Controllers to Pyramid Views
988 # translate from old Controllers to Pyramid Views
989 entry = compat[entry]
989 entry = compat[entry]
990
990
991 if '@' in entry:
991 if '@' in entry:
992 # specific AuthToken
992 # specific AuthToken
993 entry, allowed_token = entry.split('@', 1)
993 entry, allowed_token = entry.split('@', 1)
994 token_match = auth_token == allowed_token
994 token_match = auth_token == allowed_token
995
995
996 if fnmatch.fnmatch(view_name, entry) and token_match:
996 if fnmatch.fnmatch(view_name, entry) and token_match:
997 auth_token_access_valid = True
997 auth_token_access_valid = True
998 break
998 break
999
999
1000 if auth_token_access_valid:
1000 if auth_token_access_valid:
1001 log.debug('view: `%s` matches entry in whitelist: %s'
1001 log.debug('view: `%s` matches entry in whitelist: %s'
1002 % (view_name, whitelist))
1002 % (view_name, whitelist))
1003 else:
1003 else:
1004 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1004 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1005 % (view_name, whitelist))
1005 % (view_name, whitelist))
1006 if auth_token:
1006 if auth_token:
1007 # if we use auth token key and don't have access it's a warning
1007 # if we use auth token key and don't have access it's a warning
1008 log.warning(msg)
1008 log.warning(msg)
1009 else:
1009 else:
1010 log.debug(msg)
1010 log.debug(msg)
1011
1011
1012 return auth_token_access_valid
1012 return auth_token_access_valid
1013
1013
1014
1014
1015 class AuthUser(object):
1015 class AuthUser(object):
1016 """
1016 """
1017 A simple object that handles all attributes of user in RhodeCode
1017 A simple object that handles all attributes of user in RhodeCode
1018
1018
1019 It does lookup based on API key,given user, or user present in session
1019 It does lookup based on API key,given user, or user present in session
1020 Then it fills all required information for such user. It also checks if
1020 Then it fills all required information for such user. It also checks if
1021 anonymous access is enabled and if so, it returns default user as logged in
1021 anonymous access is enabled and if so, it returns default user as logged in
1022 """
1022 """
1023 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1023 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1024
1024
1025 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1025 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1026
1026
1027 self.user_id = user_id
1027 self.user_id = user_id
1028 self._api_key = api_key
1028 self._api_key = api_key
1029
1029
1030 self.api_key = None
1030 self.api_key = None
1031 self.username = username
1031 self.username = username
1032 self.ip_addr = ip_addr
1032 self.ip_addr = ip_addr
1033 self.name = ''
1033 self.name = ''
1034 self.lastname = ''
1034 self.lastname = ''
1035 self.first_name = ''
1035 self.first_name = ''
1036 self.last_name = ''
1036 self.last_name = ''
1037 self.email = ''
1037 self.email = ''
1038 self.is_authenticated = False
1038 self.is_authenticated = False
1039 self.admin = False
1039 self.admin = False
1040 self.inherit_default_permissions = False
1040 self.inherit_default_permissions = False
1041 self.password = ''
1041 self.password = ''
1042
1042
1043 self.anonymous_user = None # propagated on propagate_data
1043 self.anonymous_user = None # propagated on propagate_data
1044 self.propagate_data()
1044 self.propagate_data()
1045 self._instance = None
1045 self._instance = None
1046 self._permissions_scoped_cache = {} # used to bind scoped calculation
1046 self._permissions_scoped_cache = {} # used to bind scoped calculation
1047
1047
1048 @LazyProperty
1048 @LazyProperty
1049 def permissions(self):
1049 def permissions(self):
1050 return self.get_perms(user=self, cache=None)
1050 return self.get_perms(user=self, cache=None)
1051
1051
1052 @LazyProperty
1052 @LazyProperty
1053 def permissions_safe(self):
1053 def permissions_safe(self):
1054 """
1054 """
1055 Filtered permissions excluding not allowed repositories
1055 Filtered permissions excluding not allowed repositories
1056 """
1056 """
1057 perms = self.get_perms(user=self, cache=None)
1057 perms = self.get_perms(user=self, cache=None)
1058
1058
1059 perms['repositories'] = {
1059 perms['repositories'] = {
1060 k: v for k, v in perms['repositories'].items()
1060 k: v for k, v in perms['repositories'].items()
1061 if v != 'repository.none'}
1061 if v != 'repository.none'}
1062 perms['repositories_groups'] = {
1062 perms['repositories_groups'] = {
1063 k: v for k, v in perms['repositories_groups'].items()
1063 k: v for k, v in perms['repositories_groups'].items()
1064 if v != 'group.none'}
1064 if v != 'group.none'}
1065 perms['user_groups'] = {
1065 perms['user_groups'] = {
1066 k: v for k, v in perms['user_groups'].items()
1066 k: v for k, v in perms['user_groups'].items()
1067 if v != 'usergroup.none'}
1067 if v != 'usergroup.none'}
1068 perms['repository_branches'] = {
1068 perms['repository_branches'] = {
1069 k: v for k, v in perms['repository_branches'].iteritems()
1069 k: v for k, v in perms['repository_branches'].iteritems()
1070 if v != 'branch.none'}
1070 if v != 'branch.none'}
1071 return perms
1071 return perms
1072
1072
1073 @LazyProperty
1073 @LazyProperty
1074 def permissions_full_details(self):
1074 def permissions_full_details(self):
1075 return self.get_perms(
1075 return self.get_perms(
1076 user=self, cache=None, calculate_super_admin=True)
1076 user=self, cache=None, calculate_super_admin=True)
1077
1077
1078 def permissions_with_scope(self, scope):
1078 def permissions_with_scope(self, scope):
1079 """
1079 """
1080 Call the get_perms function with scoped data. The scope in that function
1080 Call the get_perms function with scoped data. The scope in that function
1081 narrows the SQL calls to the given ID of objects resulting in fetching
1081 narrows the SQL calls to the given ID of objects resulting in fetching
1082 Just particular permission we want to obtain. If scope is an empty dict
1082 Just particular permission we want to obtain. If scope is an empty dict
1083 then it basically narrows the scope to GLOBAL permissions only.
1083 then it basically narrows the scope to GLOBAL permissions only.
1084
1084
1085 :param scope: dict
1085 :param scope: dict
1086 """
1086 """
1087 if 'repo_name' in scope:
1087 if 'repo_name' in scope:
1088 obj = Repository.get_by_repo_name(scope['repo_name'])
1088 obj = Repository.get_by_repo_name(scope['repo_name'])
1089 if obj:
1089 if obj:
1090 scope['repo_id'] = obj.repo_id
1090 scope['repo_id'] = obj.repo_id
1091 _scope = collections.OrderedDict()
1091 _scope = collections.OrderedDict()
1092 _scope['repo_id'] = -1
1092 _scope['repo_id'] = -1
1093 _scope['user_group_id'] = -1
1093 _scope['user_group_id'] = -1
1094 _scope['repo_group_id'] = -1
1094 _scope['repo_group_id'] = -1
1095
1095
1096 for k in sorted(scope.keys()):
1096 for k in sorted(scope.keys()):
1097 _scope[k] = scope[k]
1097 _scope[k] = scope[k]
1098
1098
1099 # store in cache to mimic how the @LazyProperty works,
1099 # store in cache to mimic how the @LazyProperty works,
1100 # the difference here is that we use the unique key calculated
1100 # the difference here is that we use the unique key calculated
1101 # from params and values
1101 # from params and values
1102 return self.get_perms(user=self, cache=None, scope=_scope)
1102 return self.get_perms(user=self, cache=None, scope=_scope)
1103
1103
1104 def get_instance(self):
1104 def get_instance(self):
1105 return User.get(self.user_id)
1105 return User.get(self.user_id)
1106
1106
1107 def propagate_data(self):
1107 def propagate_data(self):
1108 """
1108 """
1109 Fills in user data and propagates values to this instance. Maps fetched
1109 Fills in user data and propagates values to this instance. Maps fetched
1110 user attributes to this class instance attributes
1110 user attributes to this class instance attributes
1111 """
1111 """
1112 log.debug('AuthUser: starting data propagation for new potential user')
1112 log.debug('AuthUser: starting data propagation for new potential user')
1113 user_model = UserModel()
1113 user_model = UserModel()
1114 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1114 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1115 is_user_loaded = False
1115 is_user_loaded = False
1116
1116
1117 # lookup by userid
1117 # lookup by userid
1118 if self.user_id is not None and self.user_id != anon_user.user_id:
1118 if self.user_id is not None and self.user_id != anon_user.user_id:
1119 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1119 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1120 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1120 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1121
1121
1122 # try go get user by api key
1122 # try go get user by api key
1123 elif self._api_key and self._api_key != anon_user.api_key:
1123 elif self._api_key and self._api_key != anon_user.api_key:
1124 log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key)
1124 log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key)
1125 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1125 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1126
1126
1127 # lookup by username
1127 # lookup by username
1128 elif self.username:
1128 elif self.username:
1129 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1129 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1130 is_user_loaded = user_model.fill_data(self, username=self.username)
1130 is_user_loaded = user_model.fill_data(self, username=self.username)
1131 else:
1131 else:
1132 log.debug('No data in %s that could been used to log in', self)
1132 log.debug('No data in %s that could been used to log in', self)
1133
1133
1134 if not is_user_loaded:
1134 if not is_user_loaded:
1135 log.debug(
1135 log.debug(
1136 'Failed to load user. Fallback to default user %s', anon_user)
1136 'Failed to load user. Fallback to default user %s', anon_user)
1137 # if we cannot authenticate user try anonymous
1137 # if we cannot authenticate user try anonymous
1138 if anon_user.active:
1138 if anon_user.active:
1139 log.debug('default user is active, using it as a session user')
1139 log.debug('default user is active, using it as a session user')
1140 user_model.fill_data(self, user_id=anon_user.user_id)
1140 user_model.fill_data(self, user_id=anon_user.user_id)
1141 # then we set this user is logged in
1141 # then we set this user is logged in
1142 self.is_authenticated = True
1142 self.is_authenticated = True
1143 else:
1143 else:
1144 log.debug('default user is NOT active')
1144 log.debug('default user is NOT active')
1145 # in case of disabled anonymous user we reset some of the
1145 # in case of disabled anonymous user we reset some of the
1146 # parameters so such user is "corrupted", skipping the fill_data
1146 # parameters so such user is "corrupted", skipping the fill_data
1147 for attr in ['user_id', 'username', 'admin', 'active']:
1147 for attr in ['user_id', 'username', 'admin', 'active']:
1148 setattr(self, attr, None)
1148 setattr(self, attr, None)
1149 self.is_authenticated = False
1149 self.is_authenticated = False
1150
1150
1151 if not self.username:
1151 if not self.username:
1152 self.username = 'None'
1152 self.username = 'None'
1153
1153
1154 log.debug('AuthUser: propagated user is now %s', self)
1154 log.debug('AuthUser: propagated user is now %s', self)
1155
1155
1156 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1156 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1157 calculate_super_admin=False, cache=None):
1157 calculate_super_admin=False, cache=None):
1158 """
1158 """
1159 Fills user permission attribute with permissions taken from database
1159 Fills user permission attribute with permissions taken from database
1160 works for permissions given for repositories, and for permissions that
1160 works for permissions given for repositories, and for permissions that
1161 are granted to groups
1161 are granted to groups
1162
1162
1163 :param user: instance of User object from database
1163 :param user: instance of User object from database
1164 :param explicit: In case there are permissions both for user and a group
1164 :param explicit: In case there are permissions both for user and a group
1165 that user is part of, explicit flag will defiine if user will
1165 that user is part of, explicit flag will defiine if user will
1166 explicitly override permissions from group, if it's False it will
1166 explicitly override permissions from group, if it's False it will
1167 make decision based on the algo
1167 make decision based on the algo
1168 :param algo: algorithm to decide what permission should be choose if
1168 :param algo: algorithm to decide what permission should be choose if
1169 it's multiple defined, eg user in two different groups. It also
1169 it's multiple defined, eg user in two different groups. It also
1170 decides if explicit flag is turned off how to specify the permission
1170 decides if explicit flag is turned off how to specify the permission
1171 for case when user is in a group + have defined separate permission
1171 for case when user is in a group + have defined separate permission
1172 :param calculate_super_admin: calculate permissions for super-admin in the
1172 :param calculate_super_admin: calculate permissions for super-admin in the
1173 same way as for regular user without speedups
1173 same way as for regular user without speedups
1174 :param cache: Use caching for calculation, None = let the cache backend decide
1174 :param cache: Use caching for calculation, None = let the cache backend decide
1175 """
1175 """
1176 user_id = user.user_id
1176 user_id = user.user_id
1177 user_is_admin = user.is_admin
1177 user_is_admin = user.is_admin
1178
1178
1179 # inheritance of global permissions like create repo/fork repo etc
1179 # inheritance of global permissions like create repo/fork repo etc
1180 user_inherit_default_permissions = user.inherit_default_permissions
1180 user_inherit_default_permissions = user.inherit_default_permissions
1181
1181
1182 cache_seconds = safe_int(
1182 cache_seconds = safe_int(
1183 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1183 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1184
1184
1185 if cache is None:
1185 if cache is None:
1186 # let the backend cache decide
1186 # let the backend cache decide
1187 cache_on = cache_seconds > 0
1187 cache_on = cache_seconds > 0
1188 else:
1188 else:
1189 cache_on = cache
1189 cache_on = cache
1190
1190
1191 log.debug(
1191 log.debug(
1192 'Computing PERMISSION tree for user %s scope `%s` '
1192 'Computing PERMISSION tree for user %s scope `%s` '
1193 'with caching: %s[TTL: %ss]' % (user, scope, cache_on, cache_seconds or 0))
1193 'with caching: %s[TTL: %ss]' % (user, scope, cache_on, cache_seconds or 0))
1194
1194
1195 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1195 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1196 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1196 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1197
1197
1198 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1198 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1199 condition=cache_on)
1199 condition=cache_on)
1200 def compute_perm_tree(cache_name,
1200 def compute_perm_tree(cache_name,
1201 user_id, scope, user_is_admin,user_inherit_default_permissions,
1201 user_id, scope, user_is_admin,user_inherit_default_permissions,
1202 explicit, algo, calculate_super_admin):
1202 explicit, algo, calculate_super_admin):
1203 return _cached_perms_data(
1203 return _cached_perms_data(
1204 user_id, scope, user_is_admin, user_inherit_default_permissions,
1204 user_id, scope, user_is_admin, user_inherit_default_permissions,
1205 explicit, algo, calculate_super_admin)
1205 explicit, algo, calculate_super_admin)
1206
1206
1207 start = time.time()
1207 start = time.time()
1208 result = compute_perm_tree(
1208 result = compute_perm_tree(
1209 'permissions', user_id, scope, user_is_admin,
1209 'permissions', user_id, scope, user_is_admin,
1210 user_inherit_default_permissions, explicit, algo,
1210 user_inherit_default_permissions, explicit, algo,
1211 calculate_super_admin)
1211 calculate_super_admin)
1212
1212
1213 result_repr = []
1213 result_repr = []
1214 for k in result:
1214 for k in result:
1215 result_repr.append((k, len(result[k])))
1215 result_repr.append((k, len(result[k])))
1216 total = time.time() - start
1216 total = time.time() - start
1217 log.debug('PERMISSION tree for user %s computed in %.3fs: %s' % (
1217 log.debug('PERMISSION tree for user %s computed in %.3fs: %s' % (
1218 user, total, result_repr))
1218 user, total, result_repr))
1219
1219
1220 return result
1220 return result
1221
1221
1222 @property
1222 @property
1223 def is_default(self):
1223 def is_default(self):
1224 return self.username == User.DEFAULT_USER
1224 return self.username == User.DEFAULT_USER
1225
1225
1226 @property
1226 @property
1227 def is_admin(self):
1227 def is_admin(self):
1228 return self.admin
1228 return self.admin
1229
1229
1230 @property
1230 @property
1231 def is_user_object(self):
1231 def is_user_object(self):
1232 return self.user_id is not None
1232 return self.user_id is not None
1233
1233
1234 @property
1234 @property
1235 def repositories_admin(self):
1235 def repositories_admin(self):
1236 """
1236 """
1237 Returns list of repositories you're an admin of
1237 Returns list of repositories you're an admin of
1238 """
1238 """
1239 return [
1239 return [
1240 x[0] for x in self.permissions['repositories'].items()
1240 x[0] for x in self.permissions['repositories'].items()
1241 if x[1] == 'repository.admin']
1241 if x[1] == 'repository.admin']
1242
1242
1243 @property
1243 @property
1244 def repository_groups_admin(self):
1244 def repository_groups_admin(self):
1245 """
1245 """
1246 Returns list of repository groups you're an admin of
1246 Returns list of repository groups you're an admin of
1247 """
1247 """
1248 return [
1248 return [
1249 x[0] for x in self.permissions['repositories_groups'].items()
1249 x[0] for x in self.permissions['repositories_groups'].items()
1250 if x[1] == 'group.admin']
1250 if x[1] == 'group.admin']
1251
1251
1252 @property
1252 @property
1253 def user_groups_admin(self):
1253 def user_groups_admin(self):
1254 """
1254 """
1255 Returns list of user groups you're an admin of
1255 Returns list of user groups you're an admin of
1256 """
1256 """
1257 return [
1257 return [
1258 x[0] for x in self.permissions['user_groups'].items()
1258 x[0] for x in self.permissions['user_groups'].items()
1259 if x[1] == 'usergroup.admin']
1259 if x[1] == 'usergroup.admin']
1260
1260
1261 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1261 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1262 """
1262 """
1263 Returns list of repository ids that user have access to based on given
1263 Returns list of repository ids that user have access to based on given
1264 perms. The cache flag should be only used in cases that are used for
1264 perms. The cache flag should be only used in cases that are used for
1265 display purposes, NOT IN ANY CASE for permission checks.
1265 display purposes, NOT IN ANY CASE for permission checks.
1266 """
1266 """
1267 from rhodecode.model.scm import RepoList
1267 from rhodecode.model.scm import RepoList
1268 if not perms:
1268 if not perms:
1269 perms = [
1269 perms = [
1270 'repository.read', 'repository.write', 'repository.admin']
1270 'repository.read', 'repository.write', 'repository.admin']
1271
1271
1272 def _cached_repo_acl(user_id, perm_def, _name_filter):
1272 def _cached_repo_acl(user_id, perm_def, _name_filter):
1273 qry = Repository.query()
1273 qry = Repository.query()
1274 if _name_filter:
1274 if _name_filter:
1275 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1275 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1276 qry = qry.filter(
1276 qry = qry.filter(
1277 Repository.repo_name.ilike(ilike_expression))
1277 Repository.repo_name.ilike(ilike_expression))
1278
1278
1279 return [x.repo_id for x in
1279 return [x.repo_id for x in
1280 RepoList(qry, perm_set=perm_def)]
1280 RepoList(qry, perm_set=perm_def)]
1281
1281
1282 return _cached_repo_acl(self.user_id, perms, name_filter)
1282 return _cached_repo_acl(self.user_id, perms, name_filter)
1283
1283
1284 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1284 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1285 """
1285 """
1286 Returns list of repository group ids that user have access to based on given
1286 Returns list of repository group ids that user have access to based on given
1287 perms. The cache flag should be only used in cases that are used for
1287 perms. The cache flag should be only used in cases that are used for
1288 display purposes, NOT IN ANY CASE for permission checks.
1288 display purposes, NOT IN ANY CASE for permission checks.
1289 """
1289 """
1290 from rhodecode.model.scm import RepoGroupList
1290 from rhodecode.model.scm import RepoGroupList
1291 if not perms:
1291 if not perms:
1292 perms = [
1292 perms = [
1293 'group.read', 'group.write', 'group.admin']
1293 'group.read', 'group.write', 'group.admin']
1294
1294
1295 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1295 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1296 qry = RepoGroup.query()
1296 qry = RepoGroup.query()
1297 if _name_filter:
1297 if _name_filter:
1298 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1298 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1299 qry = qry.filter(
1299 qry = qry.filter(
1300 RepoGroup.group_name.ilike(ilike_expression))
1300 RepoGroup.group_name.ilike(ilike_expression))
1301
1301
1302 return [x.group_id for x in
1302 return [x.group_id for x in
1303 RepoGroupList(qry, perm_set=perm_def)]
1303 RepoGroupList(qry, perm_set=perm_def)]
1304
1304
1305 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1305 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1306
1306
1307 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1307 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1308 """
1308 """
1309 Returns list of user group ids that user have access to based on given
1309 Returns list of user group ids that user have access to based on given
1310 perms. The cache flag should be only used in cases that are used for
1310 perms. The cache flag should be only used in cases that are used for
1311 display purposes, NOT IN ANY CASE for permission checks.
1311 display purposes, NOT IN ANY CASE for permission checks.
1312 """
1312 """
1313 from rhodecode.model.scm import UserGroupList
1313 from rhodecode.model.scm import UserGroupList
1314 if not perms:
1314 if not perms:
1315 perms = [
1315 perms = [
1316 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1316 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1317
1317
1318 def _cached_user_group_acl(user_id, perm_def, name_filter):
1318 def _cached_user_group_acl(user_id, perm_def, name_filter):
1319 qry = UserGroup.query()
1319 qry = UserGroup.query()
1320 if name_filter:
1320 if name_filter:
1321 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1321 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1322 qry = qry.filter(
1322 qry = qry.filter(
1323 UserGroup.users_group_name.ilike(ilike_expression))
1323 UserGroup.users_group_name.ilike(ilike_expression))
1324
1324
1325 return [x.users_group_id for x in
1325 return [x.users_group_id for x in
1326 UserGroupList(qry, perm_set=perm_def)]
1326 UserGroupList(qry, perm_set=perm_def)]
1327
1327
1328 return _cached_user_group_acl(self.user_id, perms, name_filter)
1328 return _cached_user_group_acl(self.user_id, perms, name_filter)
1329
1329
1330 @property
1330 @property
1331 def ip_allowed(self):
1331 def ip_allowed(self):
1332 """
1332 """
1333 Checks if ip_addr used in constructor is allowed from defined list of
1333 Checks if ip_addr used in constructor is allowed from defined list of
1334 allowed ip_addresses for user
1334 allowed ip_addresses for user
1335
1335
1336 :returns: boolean, True if ip is in allowed ip range
1336 :returns: boolean, True if ip is in allowed ip range
1337 """
1337 """
1338 # check IP
1338 # check IP
1339 inherit = self.inherit_default_permissions
1339 inherit = self.inherit_default_permissions
1340 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1340 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1341 inherit_from_default=inherit)
1341 inherit_from_default=inherit)
1342 @property
1342 @property
1343 def personal_repo_group(self):
1343 def personal_repo_group(self):
1344 return RepoGroup.get_user_personal_repo_group(self.user_id)
1344 return RepoGroup.get_user_personal_repo_group(self.user_id)
1345
1345
1346 @LazyProperty
1346 @LazyProperty
1347 def feed_token(self):
1347 def feed_token(self):
1348 return self.get_instance().feed_token
1348 return self.get_instance().feed_token
1349
1349
1350 @classmethod
1350 @classmethod
1351 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1351 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1352 allowed_ips = AuthUser.get_allowed_ips(
1352 allowed_ips = AuthUser.get_allowed_ips(
1353 user_id, cache=True, inherit_from_default=inherit_from_default)
1353 user_id, cache=True, inherit_from_default=inherit_from_default)
1354 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1354 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1355 log.debug('IP:%s for user %s is in range of %s' % (
1355 log.debug('IP:%s for user %s is in range of %s' % (
1356 ip_addr, user_id, allowed_ips))
1356 ip_addr, user_id, allowed_ips))
1357 return True
1357 return True
1358 else:
1358 else:
1359 log.info('Access for IP:%s forbidden for user %s, '
1359 log.info('Access for IP:%s forbidden for user %s, '
1360 'not in %s' % (ip_addr, user_id, allowed_ips))
1360 'not in %s' % (ip_addr, user_id, allowed_ips))
1361 return False
1361 return False
1362
1362
1363 def get_branch_permissions(self, repo_name, perms=None):
1363 def get_branch_permissions(self, repo_name, perms=None):
1364 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1364 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1365 branch_perms = perms.get('repository_branches')
1365 branch_perms = perms.get('repository_branches', {})
1366 return branch_perms
1366 if not branch_perms:
1367 return {}
1368 repo_branch_perms = branch_perms.get(repo_name)
1369 return repo_branch_perms or {}
1367
1370
1368 def get_rule_and_branch_permission(self, repo_name, branch_name):
1371 def get_rule_and_branch_permission(self, repo_name, branch_name):
1369 """
1372 """
1370 Check if this AuthUser has defined any permissions for branches. If any of
1373 Check if this AuthUser has defined any permissions for branches. If any of
1371 the rules match in order, we return the matching permissions
1374 the rules match in order, we return the matching permissions
1372 """
1375 """
1373
1376
1374 rule = default_perm = ''
1377 rule = default_perm = ''
1375
1378
1376 branch_perms = self.get_branch_permissions(repo_name=repo_name)
1379 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1377 if not branch_perms:
1378 return rule, default_perm
1379
1380 repo_branch_perms = branch_perms.get(repo_name)
1381 if not repo_branch_perms:
1380 if not repo_branch_perms:
1382 return rule, default_perm
1381 return rule, default_perm
1383
1382
1384 # now calculate the permissions
1383 # now calculate the permissions
1385 for pattern, branch_perm in repo_branch_perms.items():
1384 for pattern, branch_perm in repo_branch_perms.items():
1386 if fnmatch.fnmatch(branch_name, pattern):
1385 if fnmatch.fnmatch(branch_name, pattern):
1387 rule = '`{}`=>{}'.format(pattern, branch_perm)
1386 rule = '`{}`=>{}'.format(pattern, branch_perm)
1388 return rule, branch_perm
1387 return rule, branch_perm
1389
1388
1390 return rule, default_perm
1389 return rule, default_perm
1391
1390
1392 def __repr__(self):
1391 def __repr__(self):
1393 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1392 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1394 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1393 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1395
1394
1396 def set_authenticated(self, authenticated=True):
1395 def set_authenticated(self, authenticated=True):
1397 if self.user_id != self.anonymous_user.user_id:
1396 if self.user_id != self.anonymous_user.user_id:
1398 self.is_authenticated = authenticated
1397 self.is_authenticated = authenticated
1399
1398
1400 def get_cookie_store(self):
1399 def get_cookie_store(self):
1401 return {
1400 return {
1402 'username': self.username,
1401 'username': self.username,
1403 'password': md5(self.password or ''),
1402 'password': md5(self.password or ''),
1404 'user_id': self.user_id,
1403 'user_id': self.user_id,
1405 'is_authenticated': self.is_authenticated
1404 'is_authenticated': self.is_authenticated
1406 }
1405 }
1407
1406
1408 @classmethod
1407 @classmethod
1409 def from_cookie_store(cls, cookie_store):
1408 def from_cookie_store(cls, cookie_store):
1410 """
1409 """
1411 Creates AuthUser from a cookie store
1410 Creates AuthUser from a cookie store
1412
1411
1413 :param cls:
1412 :param cls:
1414 :param cookie_store:
1413 :param cookie_store:
1415 """
1414 """
1416 user_id = cookie_store.get('user_id')
1415 user_id = cookie_store.get('user_id')
1417 username = cookie_store.get('username')
1416 username = cookie_store.get('username')
1418 api_key = cookie_store.get('api_key')
1417 api_key = cookie_store.get('api_key')
1419 return AuthUser(user_id, api_key, username)
1418 return AuthUser(user_id, api_key, username)
1420
1419
1421 @classmethod
1420 @classmethod
1422 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1421 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1423 _set = set()
1422 _set = set()
1424
1423
1425 if inherit_from_default:
1424 if inherit_from_default:
1426 def_user_id = User.get_default_user(cache=True).user_id
1425 def_user_id = User.get_default_user(cache=True).user_id
1427 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1426 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1428 if cache:
1427 if cache:
1429 default_ips = default_ips.options(
1428 default_ips = default_ips.options(
1430 FromCache("sql_cache_short", "get_user_ips_default"))
1429 FromCache("sql_cache_short", "get_user_ips_default"))
1431
1430
1432 # populate from default user
1431 # populate from default user
1433 for ip in default_ips:
1432 for ip in default_ips:
1434 try:
1433 try:
1435 _set.add(ip.ip_addr)
1434 _set.add(ip.ip_addr)
1436 except ObjectDeletedError:
1435 except ObjectDeletedError:
1437 # since we use heavy caching sometimes it happens that
1436 # since we use heavy caching sometimes it happens that
1438 # we get deleted objects here, we just skip them
1437 # we get deleted objects here, we just skip them
1439 pass
1438 pass
1440
1439
1441 # NOTE:(marcink) we don't want to load any rules for empty
1440 # NOTE:(marcink) we don't want to load any rules for empty
1442 # user_id which is the case of access of non logged users when anonymous
1441 # user_id which is the case of access of non logged users when anonymous
1443 # access is disabled
1442 # access is disabled
1444 user_ips = []
1443 user_ips = []
1445 if user_id:
1444 if user_id:
1446 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1445 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1447 if cache:
1446 if cache:
1448 user_ips = user_ips.options(
1447 user_ips = user_ips.options(
1449 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1448 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1450
1449
1451 for ip in user_ips:
1450 for ip in user_ips:
1452 try:
1451 try:
1453 _set.add(ip.ip_addr)
1452 _set.add(ip.ip_addr)
1454 except ObjectDeletedError:
1453 except ObjectDeletedError:
1455 # since we use heavy caching sometimes it happens that we get
1454 # since we use heavy caching sometimes it happens that we get
1456 # deleted objects here, we just skip them
1455 # deleted objects here, we just skip them
1457 pass
1456 pass
1458 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1457 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1459
1458
1460
1459
1461 def set_available_permissions(settings):
1460 def set_available_permissions(settings):
1462 """
1461 """
1463 This function will propagate pyramid settings with all available defined
1462 This function will propagate pyramid settings with all available defined
1464 permission given in db. We don't want to check each time from db for new
1463 permission given in db. We don't want to check each time from db for new
1465 permissions since adding a new permission also requires application restart
1464 permissions since adding a new permission also requires application restart
1466 ie. to decorate new views with the newly created permission
1465 ie. to decorate new views with the newly created permission
1467
1466
1468 :param settings: current pyramid registry.settings
1467 :param settings: current pyramid registry.settings
1469
1468
1470 """
1469 """
1471 log.debug('auth: getting information about all available permissions')
1470 log.debug('auth: getting information about all available permissions')
1472 try:
1471 try:
1473 sa = meta.Session
1472 sa = meta.Session
1474 all_perms = sa.query(Permission).all()
1473 all_perms = sa.query(Permission).all()
1475 settings.setdefault('available_permissions',
1474 settings.setdefault('available_permissions',
1476 [x.permission_name for x in all_perms])
1475 [x.permission_name for x in all_perms])
1477 log.debug('auth: set available permissions')
1476 log.debug('auth: set available permissions')
1478 except Exception:
1477 except Exception:
1479 log.exception('Failed to fetch permissions from the database.')
1478 log.exception('Failed to fetch permissions from the database.')
1480 raise
1479 raise
1481
1480
1482
1481
1483 def get_csrf_token(session, force_new=False, save_if_missing=True):
1482 def get_csrf_token(session, force_new=False, save_if_missing=True):
1484 """
1483 """
1485 Return the current authentication token, creating one if one doesn't
1484 Return the current authentication token, creating one if one doesn't
1486 already exist and the save_if_missing flag is present.
1485 already exist and the save_if_missing flag is present.
1487
1486
1488 :param session: pass in the pyramid session, else we use the global ones
1487 :param session: pass in the pyramid session, else we use the global ones
1489 :param force_new: force to re-generate the token and store it in session
1488 :param force_new: force to re-generate the token and store it in session
1490 :param save_if_missing: save the newly generated token if it's missing in
1489 :param save_if_missing: save the newly generated token if it's missing in
1491 session
1490 session
1492 """
1491 """
1493 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1492 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1494 # from pyramid.csrf import get_csrf_token
1493 # from pyramid.csrf import get_csrf_token
1495
1494
1496 if (csrf_token_key not in session and save_if_missing) or force_new:
1495 if (csrf_token_key not in session and save_if_missing) or force_new:
1497 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1496 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1498 session[csrf_token_key] = token
1497 session[csrf_token_key] = token
1499 if hasattr(session, 'save'):
1498 if hasattr(session, 'save'):
1500 session.save()
1499 session.save()
1501 return session.get(csrf_token_key)
1500 return session.get(csrf_token_key)
1502
1501
1503
1502
1504 def get_request(perm_class_instance):
1503 def get_request(perm_class_instance):
1505 from pyramid.threadlocal import get_current_request
1504 from pyramid.threadlocal import get_current_request
1506 pyramid_request = get_current_request()
1505 pyramid_request = get_current_request()
1507 return pyramid_request
1506 return pyramid_request
1508
1507
1509
1508
1510 # CHECK DECORATORS
1509 # CHECK DECORATORS
1511 class CSRFRequired(object):
1510 class CSRFRequired(object):
1512 """
1511 """
1513 Decorator for authenticating a form
1512 Decorator for authenticating a form
1514
1513
1515 This decorator uses an authorization token stored in the client's
1514 This decorator uses an authorization token stored in the client's
1516 session for prevention of certain Cross-site request forgery (CSRF)
1515 session for prevention of certain Cross-site request forgery (CSRF)
1517 attacks (See
1516 attacks (See
1518 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1517 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1519 information).
1518 information).
1520
1519
1521 For use with the ``webhelpers.secure_form`` helper functions.
1520 For use with the ``webhelpers.secure_form`` helper functions.
1522
1521
1523 """
1522 """
1524 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1523 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1525 except_methods=None):
1524 except_methods=None):
1526 self.token = token
1525 self.token = token
1527 self.header = header
1526 self.header = header
1528 self.except_methods = except_methods or []
1527 self.except_methods = except_methods or []
1529
1528
1530 def __call__(self, func):
1529 def __call__(self, func):
1531 return get_cython_compat_decorator(self.__wrapper, func)
1530 return get_cython_compat_decorator(self.__wrapper, func)
1532
1531
1533 def _get_csrf(self, _request):
1532 def _get_csrf(self, _request):
1534 return _request.POST.get(self.token, _request.headers.get(self.header))
1533 return _request.POST.get(self.token, _request.headers.get(self.header))
1535
1534
1536 def check_csrf(self, _request, cur_token):
1535 def check_csrf(self, _request, cur_token):
1537 supplied_token = self._get_csrf(_request)
1536 supplied_token = self._get_csrf(_request)
1538 return supplied_token and supplied_token == cur_token
1537 return supplied_token and supplied_token == cur_token
1539
1538
1540 def _get_request(self):
1539 def _get_request(self):
1541 return get_request(self)
1540 return get_request(self)
1542
1541
1543 def __wrapper(self, func, *fargs, **fkwargs):
1542 def __wrapper(self, func, *fargs, **fkwargs):
1544 request = self._get_request()
1543 request = self._get_request()
1545
1544
1546 if request.method in self.except_methods:
1545 if request.method in self.except_methods:
1547 return func(*fargs, **fkwargs)
1546 return func(*fargs, **fkwargs)
1548
1547
1549 cur_token = get_csrf_token(request.session, save_if_missing=False)
1548 cur_token = get_csrf_token(request.session, save_if_missing=False)
1550 if self.check_csrf(request, cur_token):
1549 if self.check_csrf(request, cur_token):
1551 if request.POST.get(self.token):
1550 if request.POST.get(self.token):
1552 del request.POST[self.token]
1551 del request.POST[self.token]
1553 return func(*fargs, **fkwargs)
1552 return func(*fargs, **fkwargs)
1554 else:
1553 else:
1555 reason = 'token-missing'
1554 reason = 'token-missing'
1556 supplied_token = self._get_csrf(request)
1555 supplied_token = self._get_csrf(request)
1557 if supplied_token and cur_token != supplied_token:
1556 if supplied_token and cur_token != supplied_token:
1558 reason = 'token-mismatch [%s:%s]' % (
1557 reason = 'token-mismatch [%s:%s]' % (
1559 cur_token or ''[:6], supplied_token or ''[:6])
1558 cur_token or ''[:6], supplied_token or ''[:6])
1560
1559
1561 csrf_message = \
1560 csrf_message = \
1562 ("Cross-site request forgery detected, request denied. See "
1561 ("Cross-site request forgery detected, request denied. See "
1563 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1562 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1564 "more information.")
1563 "more information.")
1565 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1564 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1566 'REMOTE_ADDR:%s, HEADERS:%s' % (
1565 'REMOTE_ADDR:%s, HEADERS:%s' % (
1567 request, reason, request.remote_addr, request.headers))
1566 request, reason, request.remote_addr, request.headers))
1568
1567
1569 raise HTTPForbidden(explanation=csrf_message)
1568 raise HTTPForbidden(explanation=csrf_message)
1570
1569
1571
1570
1572 class LoginRequired(object):
1571 class LoginRequired(object):
1573 """
1572 """
1574 Must be logged in to execute this function else
1573 Must be logged in to execute this function else
1575 redirect to login page
1574 redirect to login page
1576
1575
1577 :param api_access: if enabled this checks only for valid auth token
1576 :param api_access: if enabled this checks only for valid auth token
1578 and grants access based on valid token
1577 and grants access based on valid token
1579 """
1578 """
1580 def __init__(self, auth_token_access=None):
1579 def __init__(self, auth_token_access=None):
1581 self.auth_token_access = auth_token_access
1580 self.auth_token_access = auth_token_access
1582
1581
1583 def __call__(self, func):
1582 def __call__(self, func):
1584 return get_cython_compat_decorator(self.__wrapper, func)
1583 return get_cython_compat_decorator(self.__wrapper, func)
1585
1584
1586 def _get_request(self):
1585 def _get_request(self):
1587 return get_request(self)
1586 return get_request(self)
1588
1587
1589 def __wrapper(self, func, *fargs, **fkwargs):
1588 def __wrapper(self, func, *fargs, **fkwargs):
1590 from rhodecode.lib import helpers as h
1589 from rhodecode.lib import helpers as h
1591 cls = fargs[0]
1590 cls = fargs[0]
1592 user = cls._rhodecode_user
1591 user = cls._rhodecode_user
1593 request = self._get_request()
1592 request = self._get_request()
1594 _ = request.translate
1593 _ = request.translate
1595
1594
1596 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1595 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1597 log.debug('Starting login restriction checks for user: %s' % (user,))
1596 log.debug('Starting login restriction checks for user: %s' % (user,))
1598 # check if our IP is allowed
1597 # check if our IP is allowed
1599 ip_access_valid = True
1598 ip_access_valid = True
1600 if not user.ip_allowed:
1599 if not user.ip_allowed:
1601 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1600 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1602 category='warning')
1601 category='warning')
1603 ip_access_valid = False
1602 ip_access_valid = False
1604
1603
1605 # check if we used an APIKEY and it's a valid one
1604 # check if we used an APIKEY and it's a valid one
1606 # defined white-list of controllers which API access will be enabled
1605 # defined white-list of controllers which API access will be enabled
1607 _auth_token = request.GET.get(
1606 _auth_token = request.GET.get(
1608 'auth_token', '') or request.GET.get('api_key', '')
1607 'auth_token', '') or request.GET.get('api_key', '')
1609 auth_token_access_valid = allowed_auth_token_access(
1608 auth_token_access_valid = allowed_auth_token_access(
1610 loc, auth_token=_auth_token)
1609 loc, auth_token=_auth_token)
1611
1610
1612 # explicit controller is enabled or API is in our whitelist
1611 # explicit controller is enabled or API is in our whitelist
1613 if self.auth_token_access or auth_token_access_valid:
1612 if self.auth_token_access or auth_token_access_valid:
1614 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1613 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1615 db_user = user.get_instance()
1614 db_user = user.get_instance()
1616
1615
1617 if db_user:
1616 if db_user:
1618 if self.auth_token_access:
1617 if self.auth_token_access:
1619 roles = self.auth_token_access
1618 roles = self.auth_token_access
1620 else:
1619 else:
1621 roles = [UserApiKeys.ROLE_HTTP]
1620 roles = [UserApiKeys.ROLE_HTTP]
1622 token_match = db_user.authenticate_by_token(
1621 token_match = db_user.authenticate_by_token(
1623 _auth_token, roles=roles)
1622 _auth_token, roles=roles)
1624 else:
1623 else:
1625 log.debug('Unable to fetch db instance for auth user: %s', user)
1624 log.debug('Unable to fetch db instance for auth user: %s', user)
1626 token_match = False
1625 token_match = False
1627
1626
1628 if _auth_token and token_match:
1627 if _auth_token and token_match:
1629 auth_token_access_valid = True
1628 auth_token_access_valid = True
1630 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1629 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1631 else:
1630 else:
1632 auth_token_access_valid = False
1631 auth_token_access_valid = False
1633 if not _auth_token:
1632 if not _auth_token:
1634 log.debug("AUTH TOKEN *NOT* present in request")
1633 log.debug("AUTH TOKEN *NOT* present in request")
1635 else:
1634 else:
1636 log.warning(
1635 log.warning(
1637 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1636 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1638
1637
1639 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1638 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1640 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1639 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1641 else 'AUTH_TOKEN_AUTH'
1640 else 'AUTH_TOKEN_AUTH'
1642
1641
1643 if ip_access_valid and (
1642 if ip_access_valid and (
1644 user.is_authenticated or auth_token_access_valid):
1643 user.is_authenticated or auth_token_access_valid):
1645 log.info(
1644 log.info(
1646 'user %s authenticating with:%s IS authenticated on func %s'
1645 'user %s authenticating with:%s IS authenticated on func %s'
1647 % (user, reason, loc))
1646 % (user, reason, loc))
1648
1647
1649 return func(*fargs, **fkwargs)
1648 return func(*fargs, **fkwargs)
1650 else:
1649 else:
1651 log.warning(
1650 log.warning(
1652 'user %s authenticating with:%s NOT authenticated on '
1651 'user %s authenticating with:%s NOT authenticated on '
1653 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1652 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1654 % (user, reason, loc, ip_access_valid,
1653 % (user, reason, loc, ip_access_valid,
1655 auth_token_access_valid))
1654 auth_token_access_valid))
1656 # we preserve the get PARAM
1655 # we preserve the get PARAM
1657 came_from = get_came_from(request)
1656 came_from = get_came_from(request)
1658
1657
1659 log.debug('redirecting to login page with %s' % (came_from,))
1658 log.debug('redirecting to login page with %s' % (came_from,))
1660 raise HTTPFound(
1659 raise HTTPFound(
1661 h.route_path('login', _query={'came_from': came_from}))
1660 h.route_path('login', _query={'came_from': came_from}))
1662
1661
1663
1662
1664 class NotAnonymous(object):
1663 class NotAnonymous(object):
1665 """
1664 """
1666 Must be logged in to execute this function else
1665 Must be logged in to execute this function else
1667 redirect to login page
1666 redirect to login page
1668 """
1667 """
1669
1668
1670 def __call__(self, func):
1669 def __call__(self, func):
1671 return get_cython_compat_decorator(self.__wrapper, func)
1670 return get_cython_compat_decorator(self.__wrapper, func)
1672
1671
1673 def _get_request(self):
1672 def _get_request(self):
1674 return get_request(self)
1673 return get_request(self)
1675
1674
1676 def __wrapper(self, func, *fargs, **fkwargs):
1675 def __wrapper(self, func, *fargs, **fkwargs):
1677 import rhodecode.lib.helpers as h
1676 import rhodecode.lib.helpers as h
1678 cls = fargs[0]
1677 cls = fargs[0]
1679 self.user = cls._rhodecode_user
1678 self.user = cls._rhodecode_user
1680 request = self._get_request()
1679 request = self._get_request()
1681 _ = request.translate
1680 _ = request.translate
1682 log.debug('Checking if user is not anonymous @%s' % cls)
1681 log.debug('Checking if user is not anonymous @%s' % cls)
1683
1682
1684 anonymous = self.user.username == User.DEFAULT_USER
1683 anonymous = self.user.username == User.DEFAULT_USER
1685
1684
1686 if anonymous:
1685 if anonymous:
1687 came_from = get_came_from(request)
1686 came_from = get_came_from(request)
1688 h.flash(_('You need to be a registered user to '
1687 h.flash(_('You need to be a registered user to '
1689 'perform this action'),
1688 'perform this action'),
1690 category='warning')
1689 category='warning')
1691 raise HTTPFound(
1690 raise HTTPFound(
1692 h.route_path('login', _query={'came_from': came_from}))
1691 h.route_path('login', _query={'came_from': came_from}))
1693 else:
1692 else:
1694 return func(*fargs, **fkwargs)
1693 return func(*fargs, **fkwargs)
1695
1694
1696
1695
1697 class PermsDecorator(object):
1696 class PermsDecorator(object):
1698 """
1697 """
1699 Base class for controller decorators, we extract the current user from
1698 Base class for controller decorators, we extract the current user from
1700 the class itself, which has it stored in base controllers
1699 the class itself, which has it stored in base controllers
1701 """
1700 """
1702
1701
1703 def __init__(self, *required_perms):
1702 def __init__(self, *required_perms):
1704 self.required_perms = set(required_perms)
1703 self.required_perms = set(required_perms)
1705
1704
1706 def __call__(self, func):
1705 def __call__(self, func):
1707 return get_cython_compat_decorator(self.__wrapper, func)
1706 return get_cython_compat_decorator(self.__wrapper, func)
1708
1707
1709 def _get_request(self):
1708 def _get_request(self):
1710 return get_request(self)
1709 return get_request(self)
1711
1710
1712 def __wrapper(self, func, *fargs, **fkwargs):
1711 def __wrapper(self, func, *fargs, **fkwargs):
1713 import rhodecode.lib.helpers as h
1712 import rhodecode.lib.helpers as h
1714 cls = fargs[0]
1713 cls = fargs[0]
1715 _user = cls._rhodecode_user
1714 _user = cls._rhodecode_user
1716 request = self._get_request()
1715 request = self._get_request()
1717 _ = request.translate
1716 _ = request.translate
1718
1717
1719 log.debug('checking %s permissions %s for %s %s',
1718 log.debug('checking %s permissions %s for %s %s',
1720 self.__class__.__name__, self.required_perms, cls, _user)
1719 self.__class__.__name__, self.required_perms, cls, _user)
1721
1720
1722 if self.check_permissions(_user):
1721 if self.check_permissions(_user):
1723 log.debug('Permission granted for %s %s', cls, _user)
1722 log.debug('Permission granted for %s %s', cls, _user)
1724 return func(*fargs, **fkwargs)
1723 return func(*fargs, **fkwargs)
1725
1724
1726 else:
1725 else:
1727 log.debug('Permission denied for %s %s', cls, _user)
1726 log.debug('Permission denied for %s %s', cls, _user)
1728 anonymous = _user.username == User.DEFAULT_USER
1727 anonymous = _user.username == User.DEFAULT_USER
1729
1728
1730 if anonymous:
1729 if anonymous:
1731 came_from = get_came_from(self._get_request())
1730 came_from = get_came_from(self._get_request())
1732 h.flash(_('You need to be signed in to view this page'),
1731 h.flash(_('You need to be signed in to view this page'),
1733 category='warning')
1732 category='warning')
1734 raise HTTPFound(
1733 raise HTTPFound(
1735 h.route_path('login', _query={'came_from': came_from}))
1734 h.route_path('login', _query={'came_from': came_from}))
1736
1735
1737 else:
1736 else:
1738 # redirect with 404 to prevent resource discovery
1737 # redirect with 404 to prevent resource discovery
1739 raise HTTPNotFound()
1738 raise HTTPNotFound()
1740
1739
1741 def check_permissions(self, user):
1740 def check_permissions(self, user):
1742 """Dummy function for overriding"""
1741 """Dummy function for overriding"""
1743 raise NotImplementedError(
1742 raise NotImplementedError(
1744 'You have to write this function in child class')
1743 'You have to write this function in child class')
1745
1744
1746
1745
1747 class HasPermissionAllDecorator(PermsDecorator):
1746 class HasPermissionAllDecorator(PermsDecorator):
1748 """
1747 """
1749 Checks for access permission for all given predicates. All of them
1748 Checks for access permission for all given predicates. All of them
1750 have to be meet in order to fulfill the request
1749 have to be meet in order to fulfill the request
1751 """
1750 """
1752
1751
1753 def check_permissions(self, user):
1752 def check_permissions(self, user):
1754 perms = user.permissions_with_scope({})
1753 perms = user.permissions_with_scope({})
1755 if self.required_perms.issubset(perms['global']):
1754 if self.required_perms.issubset(perms['global']):
1756 return True
1755 return True
1757 return False
1756 return False
1758
1757
1759
1758
1760 class HasPermissionAnyDecorator(PermsDecorator):
1759 class HasPermissionAnyDecorator(PermsDecorator):
1761 """
1760 """
1762 Checks for access permission for any of given predicates. In order to
1761 Checks for access permission for any of given predicates. In order to
1763 fulfill the request any of predicates must be meet
1762 fulfill the request any of predicates must be meet
1764 """
1763 """
1765
1764
1766 def check_permissions(self, user):
1765 def check_permissions(self, user):
1767 perms = user.permissions_with_scope({})
1766 perms = user.permissions_with_scope({})
1768 if self.required_perms.intersection(perms['global']):
1767 if self.required_perms.intersection(perms['global']):
1769 return True
1768 return True
1770 return False
1769 return False
1771
1770
1772
1771
1773 class HasRepoPermissionAllDecorator(PermsDecorator):
1772 class HasRepoPermissionAllDecorator(PermsDecorator):
1774 """
1773 """
1775 Checks for access permission for all given predicates for specific
1774 Checks for access permission for all given predicates for specific
1776 repository. All of them have to be meet in order to fulfill the request
1775 repository. All of them have to be meet in order to fulfill the request
1777 """
1776 """
1778 def _get_repo_name(self):
1777 def _get_repo_name(self):
1779 _request = self._get_request()
1778 _request = self._get_request()
1780 return get_repo_slug(_request)
1779 return get_repo_slug(_request)
1781
1780
1782 def check_permissions(self, user):
1781 def check_permissions(self, user):
1783 perms = user.permissions
1782 perms = user.permissions
1784 repo_name = self._get_repo_name()
1783 repo_name = self._get_repo_name()
1785
1784
1786 try:
1785 try:
1787 user_perms = {perms['repositories'][repo_name]}
1786 user_perms = {perms['repositories'][repo_name]}
1788 except KeyError:
1787 except KeyError:
1789 log.debug('cannot locate repo with name: `%s` in permissions defs',
1788 log.debug('cannot locate repo with name: `%s` in permissions defs',
1790 repo_name)
1789 repo_name)
1791 return False
1790 return False
1792
1791
1793 log.debug('checking `%s` permissions for repo `%s`',
1792 log.debug('checking `%s` permissions for repo `%s`',
1794 user_perms, repo_name)
1793 user_perms, repo_name)
1795 if self.required_perms.issubset(user_perms):
1794 if self.required_perms.issubset(user_perms):
1796 return True
1795 return True
1797 return False
1796 return False
1798
1797
1799
1798
1800 class HasRepoPermissionAnyDecorator(PermsDecorator):
1799 class HasRepoPermissionAnyDecorator(PermsDecorator):
1801 """
1800 """
1802 Checks for access permission for any of given predicates for specific
1801 Checks for access permission for any of given predicates for specific
1803 repository. In order to fulfill the request any of predicates must be meet
1802 repository. In order to fulfill the request any of predicates must be meet
1804 """
1803 """
1805 def _get_repo_name(self):
1804 def _get_repo_name(self):
1806 _request = self._get_request()
1805 _request = self._get_request()
1807 return get_repo_slug(_request)
1806 return get_repo_slug(_request)
1808
1807
1809 def check_permissions(self, user):
1808 def check_permissions(self, user):
1810 perms = user.permissions
1809 perms = user.permissions
1811 repo_name = self._get_repo_name()
1810 repo_name = self._get_repo_name()
1812
1811
1813 try:
1812 try:
1814 user_perms = {perms['repositories'][repo_name]}
1813 user_perms = {perms['repositories'][repo_name]}
1815 except KeyError:
1814 except KeyError:
1816 log.debug(
1815 log.debug(
1817 'cannot locate repo with name: `%s` in permissions defs',
1816 'cannot locate repo with name: `%s` in permissions defs',
1818 repo_name)
1817 repo_name)
1819 return False
1818 return False
1820
1819
1821 log.debug('checking `%s` permissions for repo `%s`',
1820 log.debug('checking `%s` permissions for repo `%s`',
1822 user_perms, repo_name)
1821 user_perms, repo_name)
1823 if self.required_perms.intersection(user_perms):
1822 if self.required_perms.intersection(user_perms):
1824 return True
1823 return True
1825 return False
1824 return False
1826
1825
1827
1826
1828 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1827 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1829 """
1828 """
1830 Checks for access permission for all given predicates for specific
1829 Checks for access permission for all given predicates for specific
1831 repository group. All of them have to be meet in order to
1830 repository group. All of them have to be meet in order to
1832 fulfill the request
1831 fulfill the request
1833 """
1832 """
1834 def _get_repo_group_name(self):
1833 def _get_repo_group_name(self):
1835 _request = self._get_request()
1834 _request = self._get_request()
1836 return get_repo_group_slug(_request)
1835 return get_repo_group_slug(_request)
1837
1836
1838 def check_permissions(self, user):
1837 def check_permissions(self, user):
1839 perms = user.permissions
1838 perms = user.permissions
1840 group_name = self._get_repo_group_name()
1839 group_name = self._get_repo_group_name()
1841 try:
1840 try:
1842 user_perms = {perms['repositories_groups'][group_name]}
1841 user_perms = {perms['repositories_groups'][group_name]}
1843 except KeyError:
1842 except KeyError:
1844 log.debug(
1843 log.debug(
1845 'cannot locate repo group with name: `%s` in permissions defs',
1844 'cannot locate repo group with name: `%s` in permissions defs',
1846 group_name)
1845 group_name)
1847 return False
1846 return False
1848
1847
1849 log.debug('checking `%s` permissions for repo group `%s`',
1848 log.debug('checking `%s` permissions for repo group `%s`',
1850 user_perms, group_name)
1849 user_perms, group_name)
1851 if self.required_perms.issubset(user_perms):
1850 if self.required_perms.issubset(user_perms):
1852 return True
1851 return True
1853 return False
1852 return False
1854
1853
1855
1854
1856 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1855 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1857 """
1856 """
1858 Checks for access permission for any of given predicates for specific
1857 Checks for access permission for any of given predicates for specific
1859 repository group. In order to fulfill the request any
1858 repository group. In order to fulfill the request any
1860 of predicates must be met
1859 of predicates must be met
1861 """
1860 """
1862 def _get_repo_group_name(self):
1861 def _get_repo_group_name(self):
1863 _request = self._get_request()
1862 _request = self._get_request()
1864 return get_repo_group_slug(_request)
1863 return get_repo_group_slug(_request)
1865
1864
1866 def check_permissions(self, user):
1865 def check_permissions(self, user):
1867 perms = user.permissions
1866 perms = user.permissions
1868 group_name = self._get_repo_group_name()
1867 group_name = self._get_repo_group_name()
1869
1868
1870 try:
1869 try:
1871 user_perms = {perms['repositories_groups'][group_name]}
1870 user_perms = {perms['repositories_groups'][group_name]}
1872 except KeyError:
1871 except KeyError:
1873 log.debug(
1872 log.debug(
1874 'cannot locate repo group with name: `%s` in permissions defs',
1873 'cannot locate repo group with name: `%s` in permissions defs',
1875 group_name)
1874 group_name)
1876 return False
1875 return False
1877
1876
1878 log.debug('checking `%s` permissions for repo group `%s`',
1877 log.debug('checking `%s` permissions for repo group `%s`',
1879 user_perms, group_name)
1878 user_perms, group_name)
1880 if self.required_perms.intersection(user_perms):
1879 if self.required_perms.intersection(user_perms):
1881 return True
1880 return True
1882 return False
1881 return False
1883
1882
1884
1883
1885 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1884 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1886 """
1885 """
1887 Checks for access permission for all given predicates for specific
1886 Checks for access permission for all given predicates for specific
1888 user group. All of them have to be meet in order to fulfill the request
1887 user group. All of them have to be meet in order to fulfill the request
1889 """
1888 """
1890 def _get_user_group_name(self):
1889 def _get_user_group_name(self):
1891 _request = self._get_request()
1890 _request = self._get_request()
1892 return get_user_group_slug(_request)
1891 return get_user_group_slug(_request)
1893
1892
1894 def check_permissions(self, user):
1893 def check_permissions(self, user):
1895 perms = user.permissions
1894 perms = user.permissions
1896 group_name = self._get_user_group_name()
1895 group_name = self._get_user_group_name()
1897 try:
1896 try:
1898 user_perms = {perms['user_groups'][group_name]}
1897 user_perms = {perms['user_groups'][group_name]}
1899 except KeyError:
1898 except KeyError:
1900 return False
1899 return False
1901
1900
1902 if self.required_perms.issubset(user_perms):
1901 if self.required_perms.issubset(user_perms):
1903 return True
1902 return True
1904 return False
1903 return False
1905
1904
1906
1905
1907 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1906 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1908 """
1907 """
1909 Checks for access permission for any of given predicates for specific
1908 Checks for access permission for any of given predicates for specific
1910 user group. In order to fulfill the request any of predicates must be meet
1909 user group. In order to fulfill the request any of predicates must be meet
1911 """
1910 """
1912 def _get_user_group_name(self):
1911 def _get_user_group_name(self):
1913 _request = self._get_request()
1912 _request = self._get_request()
1914 return get_user_group_slug(_request)
1913 return get_user_group_slug(_request)
1915
1914
1916 def check_permissions(self, user):
1915 def check_permissions(self, user):
1917 perms = user.permissions
1916 perms = user.permissions
1918 group_name = self._get_user_group_name()
1917 group_name = self._get_user_group_name()
1919 try:
1918 try:
1920 user_perms = {perms['user_groups'][group_name]}
1919 user_perms = {perms['user_groups'][group_name]}
1921 except KeyError:
1920 except KeyError:
1922 return False
1921 return False
1923
1922
1924 if self.required_perms.intersection(user_perms):
1923 if self.required_perms.intersection(user_perms):
1925 return True
1924 return True
1926 return False
1925 return False
1927
1926
1928
1927
1929 # CHECK FUNCTIONS
1928 # CHECK FUNCTIONS
1930 class PermsFunction(object):
1929 class PermsFunction(object):
1931 """Base function for other check functions"""
1930 """Base function for other check functions"""
1932
1931
1933 def __init__(self, *perms):
1932 def __init__(self, *perms):
1934 self.required_perms = set(perms)
1933 self.required_perms = set(perms)
1935 self.repo_name = None
1934 self.repo_name = None
1936 self.repo_group_name = None
1935 self.repo_group_name = None
1937 self.user_group_name = None
1936 self.user_group_name = None
1938
1937
1939 def __bool__(self):
1938 def __bool__(self):
1940 frame = inspect.currentframe()
1939 frame = inspect.currentframe()
1941 stack_trace = traceback.format_stack(frame)
1940 stack_trace = traceback.format_stack(frame)
1942 log.error('Checking bool value on a class instance of perm '
1941 log.error('Checking bool value on a class instance of perm '
1943 'function is not allowed: %s' % ''.join(stack_trace))
1942 'function is not allowed: %s' % ''.join(stack_trace))
1944 # rather than throwing errors, here we always return False so if by
1943 # rather than throwing errors, here we always return False so if by
1945 # accident someone checks truth for just an instance it will always end
1944 # accident someone checks truth for just an instance it will always end
1946 # up in returning False
1945 # up in returning False
1947 return False
1946 return False
1948 __nonzero__ = __bool__
1947 __nonzero__ = __bool__
1949
1948
1950 def __call__(self, check_location='', user=None):
1949 def __call__(self, check_location='', user=None):
1951 if not user:
1950 if not user:
1952 log.debug('Using user attribute from global request')
1951 log.debug('Using user attribute from global request')
1953 request = self._get_request()
1952 request = self._get_request()
1954 user = request.user
1953 user = request.user
1955
1954
1956 # init auth user if not already given
1955 # init auth user if not already given
1957 if not isinstance(user, AuthUser):
1956 if not isinstance(user, AuthUser):
1958 log.debug('Wrapping user %s into AuthUser', user)
1957 log.debug('Wrapping user %s into AuthUser', user)
1959 user = AuthUser(user.user_id)
1958 user = AuthUser(user.user_id)
1960
1959
1961 cls_name = self.__class__.__name__
1960 cls_name = self.__class__.__name__
1962 check_scope = self._get_check_scope(cls_name)
1961 check_scope = self._get_check_scope(cls_name)
1963 check_location = check_location or 'unspecified location'
1962 check_location = check_location or 'unspecified location'
1964
1963
1965 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1964 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1966 self.required_perms, user, check_scope, check_location)
1965 self.required_perms, user, check_scope, check_location)
1967 if not user:
1966 if not user:
1968 log.warning('Empty user given for permission check')
1967 log.warning('Empty user given for permission check')
1969 return False
1968 return False
1970
1969
1971 if self.check_permissions(user):
1970 if self.check_permissions(user):
1972 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1971 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1973 check_scope, user, check_location)
1972 check_scope, user, check_location)
1974 return True
1973 return True
1975
1974
1976 else:
1975 else:
1977 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1976 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1978 check_scope, user, check_location)
1977 check_scope, user, check_location)
1979 return False
1978 return False
1980
1979
1981 def _get_request(self):
1980 def _get_request(self):
1982 return get_request(self)
1981 return get_request(self)
1983
1982
1984 def _get_check_scope(self, cls_name):
1983 def _get_check_scope(self, cls_name):
1985 return {
1984 return {
1986 'HasPermissionAll': 'GLOBAL',
1985 'HasPermissionAll': 'GLOBAL',
1987 'HasPermissionAny': 'GLOBAL',
1986 'HasPermissionAny': 'GLOBAL',
1988 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1987 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1989 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1988 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1990 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1989 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1991 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1990 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1992 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1991 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1993 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1992 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1994 }.get(cls_name, '?:%s' % cls_name)
1993 }.get(cls_name, '?:%s' % cls_name)
1995
1994
1996 def check_permissions(self, user):
1995 def check_permissions(self, user):
1997 """Dummy function for overriding"""
1996 """Dummy function for overriding"""
1998 raise Exception('You have to write this function in child class')
1997 raise Exception('You have to write this function in child class')
1999
1998
2000
1999
2001 class HasPermissionAll(PermsFunction):
2000 class HasPermissionAll(PermsFunction):
2002 def check_permissions(self, user):
2001 def check_permissions(self, user):
2003 perms = user.permissions_with_scope({})
2002 perms = user.permissions_with_scope({})
2004 if self.required_perms.issubset(perms.get('global')):
2003 if self.required_perms.issubset(perms.get('global')):
2005 return True
2004 return True
2006 return False
2005 return False
2007
2006
2008
2007
2009 class HasPermissionAny(PermsFunction):
2008 class HasPermissionAny(PermsFunction):
2010 def check_permissions(self, user):
2009 def check_permissions(self, user):
2011 perms = user.permissions_with_scope({})
2010 perms = user.permissions_with_scope({})
2012 if self.required_perms.intersection(perms.get('global')):
2011 if self.required_perms.intersection(perms.get('global')):
2013 return True
2012 return True
2014 return False
2013 return False
2015
2014
2016
2015
2017 class HasRepoPermissionAll(PermsFunction):
2016 class HasRepoPermissionAll(PermsFunction):
2018 def __call__(self, repo_name=None, check_location='', user=None):
2017 def __call__(self, repo_name=None, check_location='', user=None):
2019 self.repo_name = repo_name
2018 self.repo_name = repo_name
2020 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2019 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2021
2020
2022 def _get_repo_name(self):
2021 def _get_repo_name(self):
2023 if not self.repo_name:
2022 if not self.repo_name:
2024 _request = self._get_request()
2023 _request = self._get_request()
2025 self.repo_name = get_repo_slug(_request)
2024 self.repo_name = get_repo_slug(_request)
2026 return self.repo_name
2025 return self.repo_name
2027
2026
2028 def check_permissions(self, user):
2027 def check_permissions(self, user):
2029 self.repo_name = self._get_repo_name()
2028 self.repo_name = self._get_repo_name()
2030 perms = user.permissions
2029 perms = user.permissions
2031 try:
2030 try:
2032 user_perms = {perms['repositories'][self.repo_name]}
2031 user_perms = {perms['repositories'][self.repo_name]}
2033 except KeyError:
2032 except KeyError:
2034 return False
2033 return False
2035 if self.required_perms.issubset(user_perms):
2034 if self.required_perms.issubset(user_perms):
2036 return True
2035 return True
2037 return False
2036 return False
2038
2037
2039
2038
2040 class HasRepoPermissionAny(PermsFunction):
2039 class HasRepoPermissionAny(PermsFunction):
2041 def __call__(self, repo_name=None, check_location='', user=None):
2040 def __call__(self, repo_name=None, check_location='', user=None):
2042 self.repo_name = repo_name
2041 self.repo_name = repo_name
2043 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2042 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2044
2043
2045 def _get_repo_name(self):
2044 def _get_repo_name(self):
2046 if not self.repo_name:
2045 if not self.repo_name:
2047 _request = self._get_request()
2046 _request = self._get_request()
2048 self.repo_name = get_repo_slug(_request)
2047 self.repo_name = get_repo_slug(_request)
2049 return self.repo_name
2048 return self.repo_name
2050
2049
2051 def check_permissions(self, user):
2050 def check_permissions(self, user):
2052 self.repo_name = self._get_repo_name()
2051 self.repo_name = self._get_repo_name()
2053 perms = user.permissions
2052 perms = user.permissions
2054 try:
2053 try:
2055 user_perms = {perms['repositories'][self.repo_name]}
2054 user_perms = {perms['repositories'][self.repo_name]}
2056 except KeyError:
2055 except KeyError:
2057 return False
2056 return False
2058 if self.required_perms.intersection(user_perms):
2057 if self.required_perms.intersection(user_perms):
2059 return True
2058 return True
2060 return False
2059 return False
2061
2060
2062
2061
2063 class HasRepoGroupPermissionAny(PermsFunction):
2062 class HasRepoGroupPermissionAny(PermsFunction):
2064 def __call__(self, group_name=None, check_location='', user=None):
2063 def __call__(self, group_name=None, check_location='', user=None):
2065 self.repo_group_name = group_name
2064 self.repo_group_name = group_name
2066 return super(HasRepoGroupPermissionAny, self).__call__(
2065 return super(HasRepoGroupPermissionAny, self).__call__(
2067 check_location, user)
2066 check_location, user)
2068
2067
2069 def check_permissions(self, user):
2068 def check_permissions(self, user):
2070 perms = user.permissions
2069 perms = user.permissions
2071 try:
2070 try:
2072 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2071 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2073 except KeyError:
2072 except KeyError:
2074 return False
2073 return False
2075 if self.required_perms.intersection(user_perms):
2074 if self.required_perms.intersection(user_perms):
2076 return True
2075 return True
2077 return False
2076 return False
2078
2077
2079
2078
2080 class HasRepoGroupPermissionAll(PermsFunction):
2079 class HasRepoGroupPermissionAll(PermsFunction):
2081 def __call__(self, group_name=None, check_location='', user=None):
2080 def __call__(self, group_name=None, check_location='', user=None):
2082 self.repo_group_name = group_name
2081 self.repo_group_name = group_name
2083 return super(HasRepoGroupPermissionAll, self).__call__(
2082 return super(HasRepoGroupPermissionAll, self).__call__(
2084 check_location, user)
2083 check_location, user)
2085
2084
2086 def check_permissions(self, user):
2085 def check_permissions(self, user):
2087 perms = user.permissions
2086 perms = user.permissions
2088 try:
2087 try:
2089 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2088 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2090 except KeyError:
2089 except KeyError:
2091 return False
2090 return False
2092 if self.required_perms.issubset(user_perms):
2091 if self.required_perms.issubset(user_perms):
2093 return True
2092 return True
2094 return False
2093 return False
2095
2094
2096
2095
2097 class HasUserGroupPermissionAny(PermsFunction):
2096 class HasUserGroupPermissionAny(PermsFunction):
2098 def __call__(self, user_group_name=None, check_location='', user=None):
2097 def __call__(self, user_group_name=None, check_location='', user=None):
2099 self.user_group_name = user_group_name
2098 self.user_group_name = user_group_name
2100 return super(HasUserGroupPermissionAny, self).__call__(
2099 return super(HasUserGroupPermissionAny, self).__call__(
2101 check_location, user)
2100 check_location, user)
2102
2101
2103 def check_permissions(self, user):
2102 def check_permissions(self, user):
2104 perms = user.permissions
2103 perms = user.permissions
2105 try:
2104 try:
2106 user_perms = {perms['user_groups'][self.user_group_name]}
2105 user_perms = {perms['user_groups'][self.user_group_name]}
2107 except KeyError:
2106 except KeyError:
2108 return False
2107 return False
2109 if self.required_perms.intersection(user_perms):
2108 if self.required_perms.intersection(user_perms):
2110 return True
2109 return True
2111 return False
2110 return False
2112
2111
2113
2112
2114 class HasUserGroupPermissionAll(PermsFunction):
2113 class HasUserGroupPermissionAll(PermsFunction):
2115 def __call__(self, user_group_name=None, check_location='', user=None):
2114 def __call__(self, user_group_name=None, check_location='', user=None):
2116 self.user_group_name = user_group_name
2115 self.user_group_name = user_group_name
2117 return super(HasUserGroupPermissionAll, self).__call__(
2116 return super(HasUserGroupPermissionAll, self).__call__(
2118 check_location, user)
2117 check_location, user)
2119
2118
2120 def check_permissions(self, user):
2119 def check_permissions(self, user):
2121 perms = user.permissions
2120 perms = user.permissions
2122 try:
2121 try:
2123 user_perms = {perms['user_groups'][self.user_group_name]}
2122 user_perms = {perms['user_groups'][self.user_group_name]}
2124 except KeyError:
2123 except KeyError:
2125 return False
2124 return False
2126 if self.required_perms.issubset(user_perms):
2125 if self.required_perms.issubset(user_perms):
2127 return True
2126 return True
2128 return False
2127 return False
2129
2128
2130
2129
2131 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2130 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2132 class HasPermissionAnyMiddleware(object):
2131 class HasPermissionAnyMiddleware(object):
2133 def __init__(self, *perms):
2132 def __init__(self, *perms):
2134 self.required_perms = set(perms)
2133 self.required_perms = set(perms)
2135
2134
2136 def __call__(self, auth_user, repo_name):
2135 def __call__(self, auth_user, repo_name):
2137 # repo_name MUST be unicode, since we handle keys in permission
2136 # repo_name MUST be unicode, since we handle keys in permission
2138 # dict by unicode
2137 # dict by unicode
2139 repo_name = safe_unicode(repo_name)
2138 repo_name = safe_unicode(repo_name)
2140 log.debug(
2139 log.debug(
2141 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2140 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2142 self.required_perms, auth_user, repo_name)
2141 self.required_perms, auth_user, repo_name)
2143
2142
2144 if self.check_permissions(auth_user, repo_name):
2143 if self.check_permissions(auth_user, repo_name):
2145 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2144 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2146 repo_name, auth_user, 'PermissionMiddleware')
2145 repo_name, auth_user, 'PermissionMiddleware')
2147 return True
2146 return True
2148
2147
2149 else:
2148 else:
2150 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2149 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2151 repo_name, auth_user, 'PermissionMiddleware')
2150 repo_name, auth_user, 'PermissionMiddleware')
2152 return False
2151 return False
2153
2152
2154 def check_permissions(self, user, repo_name):
2153 def check_permissions(self, user, repo_name):
2155 perms = user.permissions_with_scope({'repo_name': repo_name})
2154 perms = user.permissions_with_scope({'repo_name': repo_name})
2156
2155
2157 try:
2156 try:
2158 user_perms = {perms['repositories'][repo_name]}
2157 user_perms = {perms['repositories'][repo_name]}
2159 except Exception:
2158 except Exception:
2160 log.exception('Error while accessing user permissions')
2159 log.exception('Error while accessing user permissions')
2161 return False
2160 return False
2162
2161
2163 if self.required_perms.intersection(user_perms):
2162 if self.required_perms.intersection(user_perms):
2164 return True
2163 return True
2165 return False
2164 return False
2166
2165
2167
2166
2168 # SPECIAL VERSION TO HANDLE API AUTH
2167 # SPECIAL VERSION TO HANDLE API AUTH
2169 class _BaseApiPerm(object):
2168 class _BaseApiPerm(object):
2170 def __init__(self, *perms):
2169 def __init__(self, *perms):
2171 self.required_perms = set(perms)
2170 self.required_perms = set(perms)
2172
2171
2173 def __call__(self, check_location=None, user=None, repo_name=None,
2172 def __call__(self, check_location=None, user=None, repo_name=None,
2174 group_name=None, user_group_name=None):
2173 group_name=None, user_group_name=None):
2175 cls_name = self.__class__.__name__
2174 cls_name = self.__class__.__name__
2176 check_scope = 'global:%s' % (self.required_perms,)
2175 check_scope = 'global:%s' % (self.required_perms,)
2177 if repo_name:
2176 if repo_name:
2178 check_scope += ', repo_name:%s' % (repo_name,)
2177 check_scope += ', repo_name:%s' % (repo_name,)
2179
2178
2180 if group_name:
2179 if group_name:
2181 check_scope += ', repo_group_name:%s' % (group_name,)
2180 check_scope += ', repo_group_name:%s' % (group_name,)
2182
2181
2183 if user_group_name:
2182 if user_group_name:
2184 check_scope += ', user_group_name:%s' % (user_group_name,)
2183 check_scope += ', user_group_name:%s' % (user_group_name,)
2185
2184
2186 log.debug(
2185 log.debug(
2187 'checking cls:%s %s %s @ %s'
2186 'checking cls:%s %s %s @ %s'
2188 % (cls_name, self.required_perms, check_scope, check_location))
2187 % (cls_name, self.required_perms, check_scope, check_location))
2189 if not user:
2188 if not user:
2190 log.debug('Empty User passed into arguments')
2189 log.debug('Empty User passed into arguments')
2191 return False
2190 return False
2192
2191
2193 # process user
2192 # process user
2194 if not isinstance(user, AuthUser):
2193 if not isinstance(user, AuthUser):
2195 user = AuthUser(user.user_id)
2194 user = AuthUser(user.user_id)
2196 if not check_location:
2195 if not check_location:
2197 check_location = 'unspecified'
2196 check_location = 'unspecified'
2198 if self.check_permissions(user.permissions, repo_name, group_name,
2197 if self.check_permissions(user.permissions, repo_name, group_name,
2199 user_group_name):
2198 user_group_name):
2200 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2199 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2201 check_scope, user, check_location)
2200 check_scope, user, check_location)
2202 return True
2201 return True
2203
2202
2204 else:
2203 else:
2205 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2204 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2206 check_scope, user, check_location)
2205 check_scope, user, check_location)
2207 return False
2206 return False
2208
2207
2209 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2208 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2210 user_group_name=None):
2209 user_group_name=None):
2211 """
2210 """
2212 implement in child class should return True if permissions are ok,
2211 implement in child class should return True if permissions are ok,
2213 False otherwise
2212 False otherwise
2214
2213
2215 :param perm_defs: dict with permission definitions
2214 :param perm_defs: dict with permission definitions
2216 :param repo_name: repo name
2215 :param repo_name: repo name
2217 """
2216 """
2218 raise NotImplementedError()
2217 raise NotImplementedError()
2219
2218
2220
2219
2221 class HasPermissionAllApi(_BaseApiPerm):
2220 class HasPermissionAllApi(_BaseApiPerm):
2222 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2221 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2223 user_group_name=None):
2222 user_group_name=None):
2224 if self.required_perms.issubset(perm_defs.get('global')):
2223 if self.required_perms.issubset(perm_defs.get('global')):
2225 return True
2224 return True
2226 return False
2225 return False
2227
2226
2228
2227
2229 class HasPermissionAnyApi(_BaseApiPerm):
2228 class HasPermissionAnyApi(_BaseApiPerm):
2230 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2229 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2231 user_group_name=None):
2230 user_group_name=None):
2232 if self.required_perms.intersection(perm_defs.get('global')):
2231 if self.required_perms.intersection(perm_defs.get('global')):
2233 return True
2232 return True
2234 return False
2233 return False
2235
2234
2236
2235
2237 class HasRepoPermissionAllApi(_BaseApiPerm):
2236 class HasRepoPermissionAllApi(_BaseApiPerm):
2238 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2237 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2239 user_group_name=None):
2238 user_group_name=None):
2240 try:
2239 try:
2241 _user_perms = {perm_defs['repositories'][repo_name]}
2240 _user_perms = {perm_defs['repositories'][repo_name]}
2242 except KeyError:
2241 except KeyError:
2243 log.warning(traceback.format_exc())
2242 log.warning(traceback.format_exc())
2244 return False
2243 return False
2245 if self.required_perms.issubset(_user_perms):
2244 if self.required_perms.issubset(_user_perms):
2246 return True
2245 return True
2247 return False
2246 return False
2248
2247
2249
2248
2250 class HasRepoPermissionAnyApi(_BaseApiPerm):
2249 class HasRepoPermissionAnyApi(_BaseApiPerm):
2251 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2250 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2252 user_group_name=None):
2251 user_group_name=None):
2253 try:
2252 try:
2254 _user_perms = {perm_defs['repositories'][repo_name]}
2253 _user_perms = {perm_defs['repositories'][repo_name]}
2255 except KeyError:
2254 except KeyError:
2256 log.warning(traceback.format_exc())
2255 log.warning(traceback.format_exc())
2257 return False
2256 return False
2258 if self.required_perms.intersection(_user_perms):
2257 if self.required_perms.intersection(_user_perms):
2259 return True
2258 return True
2260 return False
2259 return False
2261
2260
2262
2261
2263 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2262 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2264 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2263 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2265 user_group_name=None):
2264 user_group_name=None):
2266 try:
2265 try:
2267 _user_perms = {perm_defs['repositories_groups'][group_name]}
2266 _user_perms = {perm_defs['repositories_groups'][group_name]}
2268 except KeyError:
2267 except KeyError:
2269 log.warning(traceback.format_exc())
2268 log.warning(traceback.format_exc())
2270 return False
2269 return False
2271 if self.required_perms.intersection(_user_perms):
2270 if self.required_perms.intersection(_user_perms):
2272 return True
2271 return True
2273 return False
2272 return False
2274
2273
2275
2274
2276 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2275 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2277 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2276 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2278 user_group_name=None):
2277 user_group_name=None):
2279 try:
2278 try:
2280 _user_perms = {perm_defs['repositories_groups'][group_name]}
2279 _user_perms = {perm_defs['repositories_groups'][group_name]}
2281 except KeyError:
2280 except KeyError:
2282 log.warning(traceback.format_exc())
2281 log.warning(traceback.format_exc())
2283 return False
2282 return False
2284 if self.required_perms.issubset(_user_perms):
2283 if self.required_perms.issubset(_user_perms):
2285 return True
2284 return True
2286 return False
2285 return False
2287
2286
2288
2287
2289 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2288 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2290 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2289 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2291 user_group_name=None):
2290 user_group_name=None):
2292 try:
2291 try:
2293 _user_perms = {perm_defs['user_groups'][user_group_name]}
2292 _user_perms = {perm_defs['user_groups'][user_group_name]}
2294 except KeyError:
2293 except KeyError:
2295 log.warning(traceback.format_exc())
2294 log.warning(traceback.format_exc())
2296 return False
2295 return False
2297 if self.required_perms.intersection(_user_perms):
2296 if self.required_perms.intersection(_user_perms):
2298 return True
2297 return True
2299 return False
2298 return False
2300
2299
2301
2300
2302 def check_ip_access(source_ip, allowed_ips=None):
2301 def check_ip_access(source_ip, allowed_ips=None):
2303 """
2302 """
2304 Checks if source_ip is a subnet of any of allowed_ips.
2303 Checks if source_ip is a subnet of any of allowed_ips.
2305
2304
2306 :param source_ip:
2305 :param source_ip:
2307 :param allowed_ips: list of allowed ips together with mask
2306 :param allowed_ips: list of allowed ips together with mask
2308 """
2307 """
2309 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
2308 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
2310 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2309 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2311 if isinstance(allowed_ips, (tuple, list, set)):
2310 if isinstance(allowed_ips, (tuple, list, set)):
2312 for ip in allowed_ips:
2311 for ip in allowed_ips:
2313 ip = safe_unicode(ip)
2312 ip = safe_unicode(ip)
2314 try:
2313 try:
2315 network_address = ipaddress.ip_network(ip, strict=False)
2314 network_address = ipaddress.ip_network(ip, strict=False)
2316 if source_ip_address in network_address:
2315 if source_ip_address in network_address:
2317 log.debug('IP %s is network %s' %
2316 log.debug('IP %s is network %s' %
2318 (source_ip_address, network_address))
2317 (source_ip_address, network_address))
2319 return True
2318 return True
2320 # for any case we cannot determine the IP, don't crash just
2319 # for any case we cannot determine the IP, don't crash just
2321 # skip it and log as error, we want to say forbidden still when
2320 # skip it and log as error, we want to say forbidden still when
2322 # sending bad IP
2321 # sending bad IP
2323 except Exception:
2322 except Exception:
2324 log.error(traceback.format_exc())
2323 log.error(traceback.format_exc())
2325 continue
2324 continue
2326 return False
2325 return False
2327
2326
2328
2327
2329 def get_cython_compat_decorator(wrapper, func):
2328 def get_cython_compat_decorator(wrapper, func):
2330 """
2329 """
2331 Creates a cython compatible decorator. The previously used
2330 Creates a cython compatible decorator. The previously used
2332 decorator.decorator() function seems to be incompatible with cython.
2331 decorator.decorator() function seems to be incompatible with cython.
2333
2332
2334 :param wrapper: __wrapper method of the decorator class
2333 :param wrapper: __wrapper method of the decorator class
2335 :param func: decorated function
2334 :param func: decorated function
2336 """
2335 """
2337 @wraps(func)
2336 @wraps(func)
2338 def local_wrapper(*args, **kwds):
2337 def local_wrapper(*args, **kwds):
2339 return wrapper(func, *args, **kwds)
2338 return wrapper(func, *args, **kwds)
2340 local_wrapper.__wrapped__ = func
2339 local_wrapper.__wrapped__ = func
2341 return local_wrapper
2340 return local_wrapper
2342
2341
2343
2342
@@ -1,548 +1,550 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 """
21 """
22 The base Controller API
22 The base Controller API
23 Provides the BaseController class for subclassing. And usage in different
23 Provides the BaseController class for subclassing. And usage in different
24 controllers
24 controllers
25 """
25 """
26
26
27 import logging
27 import logging
28 import socket
28 import socket
29
29
30 import markupsafe
30 import markupsafe
31 import ipaddress
31 import ipaddress
32
32
33 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36
36
37 import rhodecode
37 import rhodecode
38 from rhodecode.authentication.base import VCS_TYPE
38 from rhodecode.authentication.base import VCS_TYPE
39 from rhodecode.lib import auth, utils2
39 from rhodecode.lib import auth, utils2
40 from rhodecode.lib import helpers as h
40 from rhodecode.lib import helpers as h
41 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
41 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
42 from rhodecode.lib.exceptions import UserCreationError
42 from rhodecode.lib.exceptions import UserCreationError
43 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
43 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
44 from rhodecode.lib.utils2 import (
44 from rhodecode.lib.utils2 import (
45 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
45 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
46 from rhodecode.model.db import Repository, User, ChangesetComment
46 from rhodecode.model.db import Repository, User, ChangesetComment
47 from rhodecode.model.notification import NotificationModel
47 from rhodecode.model.notification import NotificationModel
48 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
48 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 def _filter_proxy(ip):
53 def _filter_proxy(ip):
54 """
54 """
55 Passed in IP addresses in HEADERS can be in a special format of multiple
55 Passed in IP addresses in HEADERS can be in a special format of multiple
56 ips. Those comma separated IPs are passed from various proxies in the
56 ips. Those comma separated IPs are passed from various proxies in the
57 chain of request processing. The left-most being the original client.
57 chain of request processing. The left-most being the original client.
58 We only care about the first IP which came from the org. client.
58 We only care about the first IP which came from the org. client.
59
59
60 :param ip: ip string from headers
60 :param ip: ip string from headers
61 """
61 """
62 if ',' in ip:
62 if ',' in ip:
63 _ips = ip.split(',')
63 _ips = ip.split(',')
64 _first_ip = _ips[0].strip()
64 _first_ip = _ips[0].strip()
65 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
65 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
66 return _first_ip
66 return _first_ip
67 return ip
67 return ip
68
68
69
69
70 def _filter_port(ip):
70 def _filter_port(ip):
71 """
71 """
72 Removes a port from ip, there are 4 main cases to handle here.
72 Removes a port from ip, there are 4 main cases to handle here.
73 - ipv4 eg. 127.0.0.1
73 - ipv4 eg. 127.0.0.1
74 - ipv6 eg. ::1
74 - ipv6 eg. ::1
75 - ipv4+port eg. 127.0.0.1:8080
75 - ipv4+port eg. 127.0.0.1:8080
76 - ipv6+port eg. [::1]:8080
76 - ipv6+port eg. [::1]:8080
77
77
78 :param ip:
78 :param ip:
79 """
79 """
80 def is_ipv6(ip_addr):
80 def is_ipv6(ip_addr):
81 if hasattr(socket, 'inet_pton'):
81 if hasattr(socket, 'inet_pton'):
82 try:
82 try:
83 socket.inet_pton(socket.AF_INET6, ip_addr)
83 socket.inet_pton(socket.AF_INET6, ip_addr)
84 except socket.error:
84 except socket.error:
85 return False
85 return False
86 else:
86 else:
87 # fallback to ipaddress
87 # fallback to ipaddress
88 try:
88 try:
89 ipaddress.IPv6Address(safe_unicode(ip_addr))
89 ipaddress.IPv6Address(safe_unicode(ip_addr))
90 except Exception:
90 except Exception:
91 return False
91 return False
92 return True
92 return True
93
93
94 if ':' not in ip: # must be ipv4 pure ip
94 if ':' not in ip: # must be ipv4 pure ip
95 return ip
95 return ip
96
96
97 if '[' in ip and ']' in ip: # ipv6 with port
97 if '[' in ip and ']' in ip: # ipv6 with port
98 return ip.split(']')[0][1:].lower()
98 return ip.split(']')[0][1:].lower()
99
99
100 # must be ipv6 or ipv4 with port
100 # must be ipv6 or ipv4 with port
101 if is_ipv6(ip):
101 if is_ipv6(ip):
102 return ip
102 return ip
103 else:
103 else:
104 ip, _port = ip.split(':')[:2] # means ipv4+port
104 ip, _port = ip.split(':')[:2] # means ipv4+port
105 return ip
105 return ip
106
106
107
107
108 def get_ip_addr(environ):
108 def get_ip_addr(environ):
109 proxy_key = 'HTTP_X_REAL_IP'
109 proxy_key = 'HTTP_X_REAL_IP'
110 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
110 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
111 def_key = 'REMOTE_ADDR'
111 def_key = 'REMOTE_ADDR'
112 _filters = lambda x: _filter_port(_filter_proxy(x))
112 _filters = lambda x: _filter_port(_filter_proxy(x))
113
113
114 ip = environ.get(proxy_key)
114 ip = environ.get(proxy_key)
115 if ip:
115 if ip:
116 return _filters(ip)
116 return _filters(ip)
117
117
118 ip = environ.get(proxy_key2)
118 ip = environ.get(proxy_key2)
119 if ip:
119 if ip:
120 return _filters(ip)
120 return _filters(ip)
121
121
122 ip = environ.get(def_key, '0.0.0.0')
122 ip = environ.get(def_key, '0.0.0.0')
123 return _filters(ip)
123 return _filters(ip)
124
124
125
125
126 def get_server_ip_addr(environ, log_errors=True):
126 def get_server_ip_addr(environ, log_errors=True):
127 hostname = environ.get('SERVER_NAME')
127 hostname = environ.get('SERVER_NAME')
128 try:
128 try:
129 return socket.gethostbyname(hostname)
129 return socket.gethostbyname(hostname)
130 except Exception as e:
130 except Exception as e:
131 if log_errors:
131 if log_errors:
132 # in some cases this lookup is not possible, and we don't want to
132 # in some cases this lookup is not possible, and we don't want to
133 # make it an exception in logs
133 # make it an exception in logs
134 log.exception('Could not retrieve server ip address: %s', e)
134 log.exception('Could not retrieve server ip address: %s', e)
135 return hostname
135 return hostname
136
136
137
137
138 def get_server_port(environ):
138 def get_server_port(environ):
139 return environ.get('SERVER_PORT')
139 return environ.get('SERVER_PORT')
140
140
141
141
142 def get_access_path(environ):
142 def get_access_path(environ):
143 path = environ.get('PATH_INFO')
143 path = environ.get('PATH_INFO')
144 org_req = environ.get('pylons.original_request')
144 org_req = environ.get('pylons.original_request')
145 if org_req:
145 if org_req:
146 path = org_req.environ.get('PATH_INFO')
146 path = org_req.environ.get('PATH_INFO')
147 return path
147 return path
148
148
149
149
150 def get_user_agent(environ):
150 def get_user_agent(environ):
151 return environ.get('HTTP_USER_AGENT')
151 return environ.get('HTTP_USER_AGENT')
152
152
153
153
154 def vcs_operation_context(
154 def vcs_operation_context(
155 environ, repo_name, username, action, scm, check_locking=True,
155 environ, repo_name, username, action, scm, check_locking=True,
156 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
156 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
157 """
157 """
158 Generate the context for a vcs operation, e.g. push or pull.
158 Generate the context for a vcs operation, e.g. push or pull.
159
159
160 This context is passed over the layers so that hooks triggered by the
160 This context is passed over the layers so that hooks triggered by the
161 vcs operation know details like the user, the user's IP address etc.
161 vcs operation know details like the user, the user's IP address etc.
162
162
163 :param check_locking: Allows to switch of the computation of the locking
163 :param check_locking: Allows to switch of the computation of the locking
164 data. This serves mainly the need of the simplevcs middleware to be
164 data. This serves mainly the need of the simplevcs middleware to be
165 able to disable this for certain operations.
165 able to disable this for certain operations.
166
166
167 """
167 """
168 # Tri-state value: False: unlock, None: nothing, True: lock
168 # Tri-state value: False: unlock, None: nothing, True: lock
169 make_lock = None
169 make_lock = None
170 locked_by = [None, None, None]
170 locked_by = [None, None, None]
171 is_anonymous = username == User.DEFAULT_USER
171 is_anonymous = username == User.DEFAULT_USER
172 user = User.get_by_username(username)
172 user = User.get_by_username(username)
173 if not is_anonymous and check_locking:
173 if not is_anonymous and check_locking:
174 log.debug('Checking locking on repository "%s"', repo_name)
174 log.debug('Checking locking on repository "%s"', repo_name)
175 repo = Repository.get_by_repo_name(repo_name)
175 repo = Repository.get_by_repo_name(repo_name)
176 make_lock, __, locked_by = repo.get_locking_state(
176 make_lock, __, locked_by = repo.get_locking_state(
177 action, user.user_id)
177 action, user.user_id)
178 user_id = user.user_id
178 user_id = user.user_id
179 settings_model = VcsSettingsModel(repo=repo_name)
179 settings_model = VcsSettingsModel(repo=repo_name)
180 ui_settings = settings_model.get_ui_settings()
180 ui_settings = settings_model.get_ui_settings()
181
181
182 extras = {
182 # NOTE(marcink): This should be also in sync with
183 # rhodecode/apps/ssh_support/lib/backends/base.py:update_enviroment scm_data
184 scm_data = {
183 'ip': get_ip_addr(environ),
185 'ip': get_ip_addr(environ),
184 'username': username,
186 'username': username,
185 'user_id': user_id,
187 'user_id': user_id,
186 'action': action,
188 'action': action,
187 'repository': repo_name,
189 'repository': repo_name,
188 'scm': scm,
190 'scm': scm,
189 'config': rhodecode.CONFIG['__file__'],
191 'config': rhodecode.CONFIG['__file__'],
190 'make_lock': make_lock,
192 'make_lock': make_lock,
191 'locked_by': locked_by,
193 'locked_by': locked_by,
192 'server_url': utils2.get_server_url(environ),
194 'server_url': utils2.get_server_url(environ),
193 'user_agent': get_user_agent(environ),
195 'user_agent': get_user_agent(environ),
194 'hooks': get_enabled_hook_classes(ui_settings),
196 'hooks': get_enabled_hook_classes(ui_settings),
195 'is_shadow_repo': is_shadow_repo,
197 'is_shadow_repo': is_shadow_repo,
196 'detect_force_push': detect_force_push,
198 'detect_force_push': detect_force_push,
197 'check_branch_perms': check_branch_perms,
199 'check_branch_perms': check_branch_perms,
198 }
200 }
199 return extras
201 return scm_data
200
202
201
203
202 class BasicAuth(AuthBasicAuthenticator):
204 class BasicAuth(AuthBasicAuthenticator):
203
205
204 def __init__(self, realm, authfunc, registry, auth_http_code=None,
206 def __init__(self, realm, authfunc, registry, auth_http_code=None,
205 initial_call_detection=False, acl_repo_name=None):
207 initial_call_detection=False, acl_repo_name=None):
206 self.realm = realm
208 self.realm = realm
207 self.initial_call = initial_call_detection
209 self.initial_call = initial_call_detection
208 self.authfunc = authfunc
210 self.authfunc = authfunc
209 self.registry = registry
211 self.registry = registry
210 self.acl_repo_name = acl_repo_name
212 self.acl_repo_name = acl_repo_name
211 self._rc_auth_http_code = auth_http_code
213 self._rc_auth_http_code = auth_http_code
212
214
213 def _get_response_from_code(self, http_code):
215 def _get_response_from_code(self, http_code):
214 try:
216 try:
215 return get_exception(safe_int(http_code))
217 return get_exception(safe_int(http_code))
216 except Exception:
218 except Exception:
217 log.exception('Failed to fetch response for code %s' % http_code)
219 log.exception('Failed to fetch response for code %s' % http_code)
218 return HTTPForbidden
220 return HTTPForbidden
219
221
220 def get_rc_realm(self):
222 def get_rc_realm(self):
221 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
223 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
222
224
223 def build_authentication(self):
225 def build_authentication(self):
224 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
226 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
225 if self._rc_auth_http_code and not self.initial_call:
227 if self._rc_auth_http_code and not self.initial_call:
226 # return alternative HTTP code if alternative http return code
228 # return alternative HTTP code if alternative http return code
227 # is specified in RhodeCode config, but ONLY if it's not the
229 # is specified in RhodeCode config, but ONLY if it's not the
228 # FIRST call
230 # FIRST call
229 custom_response_klass = self._get_response_from_code(
231 custom_response_klass = self._get_response_from_code(
230 self._rc_auth_http_code)
232 self._rc_auth_http_code)
231 return custom_response_klass(headers=head)
233 return custom_response_klass(headers=head)
232 return HTTPUnauthorized(headers=head)
234 return HTTPUnauthorized(headers=head)
233
235
234 def authenticate(self, environ):
236 def authenticate(self, environ):
235 authorization = AUTHORIZATION(environ)
237 authorization = AUTHORIZATION(environ)
236 if not authorization:
238 if not authorization:
237 return self.build_authentication()
239 return self.build_authentication()
238 (authmeth, auth) = authorization.split(' ', 1)
240 (authmeth, auth) = authorization.split(' ', 1)
239 if 'basic' != authmeth.lower():
241 if 'basic' != authmeth.lower():
240 return self.build_authentication()
242 return self.build_authentication()
241 auth = auth.strip().decode('base64')
243 auth = auth.strip().decode('base64')
242 _parts = auth.split(':', 1)
244 _parts = auth.split(':', 1)
243 if len(_parts) == 2:
245 if len(_parts) == 2:
244 username, password = _parts
246 username, password = _parts
245 auth_data = self.authfunc(
247 auth_data = self.authfunc(
246 username, password, environ, VCS_TYPE,
248 username, password, environ, VCS_TYPE,
247 registry=self.registry, acl_repo_name=self.acl_repo_name)
249 registry=self.registry, acl_repo_name=self.acl_repo_name)
248 if auth_data:
250 if auth_data:
249 return {'username': username, 'auth_data': auth_data}
251 return {'username': username, 'auth_data': auth_data}
250 if username and password:
252 if username and password:
251 # we mark that we actually executed authentication once, at
253 # we mark that we actually executed authentication once, at
252 # that point we can use the alternative auth code
254 # that point we can use the alternative auth code
253 self.initial_call = False
255 self.initial_call = False
254
256
255 return self.build_authentication()
257 return self.build_authentication()
256
258
257 __call__ = authenticate
259 __call__ = authenticate
258
260
259
261
260 def calculate_version_hash(config):
262 def calculate_version_hash(config):
261 return sha1(
263 return sha1(
262 config.get('beaker.session.secret', '') +
264 config.get('beaker.session.secret', '') +
263 rhodecode.__version__)[:8]
265 rhodecode.__version__)[:8]
264
266
265
267
266 def get_current_lang(request):
268 def get_current_lang(request):
267 # NOTE(marcink): remove after pyramid move
269 # NOTE(marcink): remove after pyramid move
268 try:
270 try:
269 return translation.get_lang()[0]
271 return translation.get_lang()[0]
270 except:
272 except:
271 pass
273 pass
272
274
273 return getattr(request, '_LOCALE_', request.locale_name)
275 return getattr(request, '_LOCALE_', request.locale_name)
274
276
275
277
276 def attach_context_attributes(context, request, user_id):
278 def attach_context_attributes(context, request, user_id):
277 """
279 """
278 Attach variables into template context called `c`.
280 Attach variables into template context called `c`.
279 """
281 """
280 config = request.registry.settings
282 config = request.registry.settings
281
283
282
284
283 rc_config = SettingsModel().get_all_settings(cache=True)
285 rc_config = SettingsModel().get_all_settings(cache=True)
284
286
285 context.rhodecode_version = rhodecode.__version__
287 context.rhodecode_version = rhodecode.__version__
286 context.rhodecode_edition = config.get('rhodecode.edition')
288 context.rhodecode_edition = config.get('rhodecode.edition')
287 # unique secret + version does not leak the version but keep consistency
289 # unique secret + version does not leak the version but keep consistency
288 context.rhodecode_version_hash = calculate_version_hash(config)
290 context.rhodecode_version_hash = calculate_version_hash(config)
289
291
290 # Default language set for the incoming request
292 # Default language set for the incoming request
291 context.language = get_current_lang(request)
293 context.language = get_current_lang(request)
292
294
293 # Visual options
295 # Visual options
294 context.visual = AttributeDict({})
296 context.visual = AttributeDict({})
295
297
296 # DB stored Visual Items
298 # DB stored Visual Items
297 context.visual.show_public_icon = str2bool(
299 context.visual.show_public_icon = str2bool(
298 rc_config.get('rhodecode_show_public_icon'))
300 rc_config.get('rhodecode_show_public_icon'))
299 context.visual.show_private_icon = str2bool(
301 context.visual.show_private_icon = str2bool(
300 rc_config.get('rhodecode_show_private_icon'))
302 rc_config.get('rhodecode_show_private_icon'))
301 context.visual.stylify_metatags = str2bool(
303 context.visual.stylify_metatags = str2bool(
302 rc_config.get('rhodecode_stylify_metatags'))
304 rc_config.get('rhodecode_stylify_metatags'))
303 context.visual.dashboard_items = safe_int(
305 context.visual.dashboard_items = safe_int(
304 rc_config.get('rhodecode_dashboard_items', 100))
306 rc_config.get('rhodecode_dashboard_items', 100))
305 context.visual.admin_grid_items = safe_int(
307 context.visual.admin_grid_items = safe_int(
306 rc_config.get('rhodecode_admin_grid_items', 100))
308 rc_config.get('rhodecode_admin_grid_items', 100))
307 context.visual.repository_fields = str2bool(
309 context.visual.repository_fields = str2bool(
308 rc_config.get('rhodecode_repository_fields'))
310 rc_config.get('rhodecode_repository_fields'))
309 context.visual.show_version = str2bool(
311 context.visual.show_version = str2bool(
310 rc_config.get('rhodecode_show_version'))
312 rc_config.get('rhodecode_show_version'))
311 context.visual.use_gravatar = str2bool(
313 context.visual.use_gravatar = str2bool(
312 rc_config.get('rhodecode_use_gravatar'))
314 rc_config.get('rhodecode_use_gravatar'))
313 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
315 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
314 context.visual.default_renderer = rc_config.get(
316 context.visual.default_renderer = rc_config.get(
315 'rhodecode_markup_renderer', 'rst')
317 'rhodecode_markup_renderer', 'rst')
316 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
318 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
317 context.visual.rhodecode_support_url = \
319 context.visual.rhodecode_support_url = \
318 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
320 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
319
321
320 context.visual.affected_files_cut_off = 60
322 context.visual.affected_files_cut_off = 60
321
323
322 context.pre_code = rc_config.get('rhodecode_pre_code')
324 context.pre_code = rc_config.get('rhodecode_pre_code')
323 context.post_code = rc_config.get('rhodecode_post_code')
325 context.post_code = rc_config.get('rhodecode_post_code')
324 context.rhodecode_name = rc_config.get('rhodecode_title')
326 context.rhodecode_name = rc_config.get('rhodecode_title')
325 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
327 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
326 # if we have specified default_encoding in the request, it has more
328 # if we have specified default_encoding in the request, it has more
327 # priority
329 # priority
328 if request.GET.get('default_encoding'):
330 if request.GET.get('default_encoding'):
329 context.default_encodings.insert(0, request.GET.get('default_encoding'))
331 context.default_encodings.insert(0, request.GET.get('default_encoding'))
330 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
332 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
331 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
333 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
332
334
333 # INI stored
335 # INI stored
334 context.labs_active = str2bool(
336 context.labs_active = str2bool(
335 config.get('labs_settings_active', 'false'))
337 config.get('labs_settings_active', 'false'))
336 context.ssh_enabled = str2bool(
338 context.ssh_enabled = str2bool(
337 config.get('ssh.generate_authorized_keyfile', 'false'))
339 config.get('ssh.generate_authorized_keyfile', 'false'))
338
340
339 context.visual.allow_repo_location_change = str2bool(
341 context.visual.allow_repo_location_change = str2bool(
340 config.get('allow_repo_location_change', True))
342 config.get('allow_repo_location_change', True))
341 context.visual.allow_custom_hooks_settings = str2bool(
343 context.visual.allow_custom_hooks_settings = str2bool(
342 config.get('allow_custom_hooks_settings', True))
344 config.get('allow_custom_hooks_settings', True))
343 context.debug_style = str2bool(config.get('debug_style', False))
345 context.debug_style = str2bool(config.get('debug_style', False))
344
346
345 context.rhodecode_instanceid = config.get('instance_id')
347 context.rhodecode_instanceid = config.get('instance_id')
346
348
347 context.visual.cut_off_limit_diff = safe_int(
349 context.visual.cut_off_limit_diff = safe_int(
348 config.get('cut_off_limit_diff'))
350 config.get('cut_off_limit_diff'))
349 context.visual.cut_off_limit_file = safe_int(
351 context.visual.cut_off_limit_file = safe_int(
350 config.get('cut_off_limit_file'))
352 config.get('cut_off_limit_file'))
351
353
352 # AppEnlight
354 # AppEnlight
353 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
355 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
354 context.appenlight_api_public_key = config.get(
356 context.appenlight_api_public_key = config.get(
355 'appenlight.api_public_key', '')
357 'appenlight.api_public_key', '')
356 context.appenlight_server_url = config.get('appenlight.server_url', '')
358 context.appenlight_server_url = config.get('appenlight.server_url', '')
357
359
358 # JS template context
360 # JS template context
359 context.template_context = {
361 context.template_context = {
360 'repo_name': None,
362 'repo_name': None,
361 'repo_type': None,
363 'repo_type': None,
362 'repo_landing_commit': None,
364 'repo_landing_commit': None,
363 'rhodecode_user': {
365 'rhodecode_user': {
364 'username': None,
366 'username': None,
365 'email': None,
367 'email': None,
366 'notification_status': False
368 'notification_status': False
367 },
369 },
368 'visual': {
370 'visual': {
369 'default_renderer': None
371 'default_renderer': None
370 },
372 },
371 'commit_data': {
373 'commit_data': {
372 'commit_id': None
374 'commit_id': None
373 },
375 },
374 'pull_request_data': {'pull_request_id': None},
376 'pull_request_data': {'pull_request_id': None},
375 'timeago': {
377 'timeago': {
376 'refresh_time': 120 * 1000,
378 'refresh_time': 120 * 1000,
377 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
379 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
378 },
380 },
379 'pyramid_dispatch': {
381 'pyramid_dispatch': {
380
382
381 },
383 },
382 'extra': {'plugins': {}}
384 'extra': {'plugins': {}}
383 }
385 }
384 # END CONFIG VARS
386 # END CONFIG VARS
385
387
386 diffmode = 'sideside'
388 diffmode = 'sideside'
387 if request.GET.get('diffmode'):
389 if request.GET.get('diffmode'):
388 if request.GET['diffmode'] == 'unified':
390 if request.GET['diffmode'] == 'unified':
389 diffmode = 'unified'
391 diffmode = 'unified'
390 elif request.session.get('diffmode'):
392 elif request.session.get('diffmode'):
391 diffmode = request.session['diffmode']
393 diffmode = request.session['diffmode']
392
394
393 context.diffmode = diffmode
395 context.diffmode = diffmode
394
396
395 if request.session.get('diffmode') != diffmode:
397 if request.session.get('diffmode') != diffmode:
396 request.session['diffmode'] = diffmode
398 request.session['diffmode'] = diffmode
397
399
398 context.csrf_token = auth.get_csrf_token(session=request.session)
400 context.csrf_token = auth.get_csrf_token(session=request.session)
399 context.backends = rhodecode.BACKENDS.keys()
401 context.backends = rhodecode.BACKENDS.keys()
400 context.backends.sort()
402 context.backends.sort()
401 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
403 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
402
404
403 # web case
405 # web case
404 if hasattr(request, 'user'):
406 if hasattr(request, 'user'):
405 context.auth_user = request.user
407 context.auth_user = request.user
406 context.rhodecode_user = request.user
408 context.rhodecode_user = request.user
407
409
408 # api case
410 # api case
409 if hasattr(request, 'rpc_user'):
411 if hasattr(request, 'rpc_user'):
410 context.auth_user = request.rpc_user
412 context.auth_user = request.rpc_user
411 context.rhodecode_user = request.rpc_user
413 context.rhodecode_user = request.rpc_user
412
414
413 # attach the whole call context to the request
415 # attach the whole call context to the request
414 request.call_context = context
416 request.call_context = context
415
417
416
418
417 def get_auth_user(request):
419 def get_auth_user(request):
418 environ = request.environ
420 environ = request.environ
419 session = request.session
421 session = request.session
420
422
421 ip_addr = get_ip_addr(environ)
423 ip_addr = get_ip_addr(environ)
422 # make sure that we update permissions each time we call controller
424 # make sure that we update permissions each time we call controller
423 _auth_token = (request.GET.get('auth_token', '') or
425 _auth_token = (request.GET.get('auth_token', '') or
424 request.GET.get('api_key', ''))
426 request.GET.get('api_key', ''))
425
427
426 if _auth_token:
428 if _auth_token:
427 # when using API_KEY we assume user exists, and
429 # when using API_KEY we assume user exists, and
428 # doesn't need auth based on cookies.
430 # doesn't need auth based on cookies.
429 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
431 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
430 authenticated = False
432 authenticated = False
431 else:
433 else:
432 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
434 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
433 try:
435 try:
434 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
436 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
435 ip_addr=ip_addr)
437 ip_addr=ip_addr)
436 except UserCreationError as e:
438 except UserCreationError as e:
437 h.flash(e, 'error')
439 h.flash(e, 'error')
438 # container auth or other auth functions that create users
440 # container auth or other auth functions that create users
439 # on the fly can throw this exception signaling that there's
441 # on the fly can throw this exception signaling that there's
440 # issue with user creation, explanation should be provided
442 # issue with user creation, explanation should be provided
441 # in Exception itself. We then create a simple blank
443 # in Exception itself. We then create a simple blank
442 # AuthUser
444 # AuthUser
443 auth_user = AuthUser(ip_addr=ip_addr)
445 auth_user = AuthUser(ip_addr=ip_addr)
444
446
445 # in case someone changes a password for user it triggers session
447 # in case someone changes a password for user it triggers session
446 # flush and forces a re-login
448 # flush and forces a re-login
447 if password_changed(auth_user, session):
449 if password_changed(auth_user, session):
448 session.invalidate()
450 session.invalidate()
449 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
451 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
450 auth_user = AuthUser(ip_addr=ip_addr)
452 auth_user = AuthUser(ip_addr=ip_addr)
451
453
452 authenticated = cookie_store.get('is_authenticated')
454 authenticated = cookie_store.get('is_authenticated')
453
455
454 if not auth_user.is_authenticated and auth_user.is_user_object:
456 if not auth_user.is_authenticated and auth_user.is_user_object:
455 # user is not authenticated and not empty
457 # user is not authenticated and not empty
456 auth_user.set_authenticated(authenticated)
458 auth_user.set_authenticated(authenticated)
457
459
458 return auth_user
460 return auth_user
459
461
460
462
461 def h_filter(s):
463 def h_filter(s):
462 """
464 """
463 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
465 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
464 we wrap this with additional functionality that converts None to empty
466 we wrap this with additional functionality that converts None to empty
465 strings
467 strings
466 """
468 """
467 if s is None:
469 if s is None:
468 return markupsafe.Markup()
470 return markupsafe.Markup()
469 return markupsafe.escape(s)
471 return markupsafe.escape(s)
470
472
471
473
472 def add_events_routes(config):
474 def add_events_routes(config):
473 """
475 """
474 Adds routing that can be used in events. Because some events are triggered
476 Adds routing that can be used in events. Because some events are triggered
475 outside of pyramid context, we need to bootstrap request with some
477 outside of pyramid context, we need to bootstrap request with some
476 routing registered
478 routing registered
477 """
479 """
478
480
479 from rhodecode.apps._base import ADMIN_PREFIX
481 from rhodecode.apps._base import ADMIN_PREFIX
480
482
481 config.add_route(name='home', pattern='/')
483 config.add_route(name='home', pattern='/')
482
484
483 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
485 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
484 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
486 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
485 config.add_route(name='repo_summary', pattern='/{repo_name}')
487 config.add_route(name='repo_summary', pattern='/{repo_name}')
486 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
488 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
487 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
489 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
488
490
489 config.add_route(name='pullrequest_show',
491 config.add_route(name='pullrequest_show',
490 pattern='/{repo_name}/pull-request/{pull_request_id}')
492 pattern='/{repo_name}/pull-request/{pull_request_id}')
491 config.add_route(name='pull_requests_global',
493 config.add_route(name='pull_requests_global',
492 pattern='/pull-request/{pull_request_id}')
494 pattern='/pull-request/{pull_request_id}')
493 config.add_route(name='repo_commit',
495 config.add_route(name='repo_commit',
494 pattern='/{repo_name}/changeset/{commit_id}')
496 pattern='/{repo_name}/changeset/{commit_id}')
495
497
496 config.add_route(name='repo_files',
498 config.add_route(name='repo_files',
497 pattern='/{repo_name}/files/{commit_id}/{f_path}')
499 pattern='/{repo_name}/files/{commit_id}/{f_path}')
498
500
499
501
500 def bootstrap_config(request):
502 def bootstrap_config(request):
501 import pyramid.testing
503 import pyramid.testing
502 registry = pyramid.testing.Registry('RcTestRegistry')
504 registry = pyramid.testing.Registry('RcTestRegistry')
503
505
504 config = pyramid.testing.setUp(registry=registry, request=request)
506 config = pyramid.testing.setUp(registry=registry, request=request)
505
507
506 # allow pyramid lookup in testing
508 # allow pyramid lookup in testing
507 config.include('pyramid_mako')
509 config.include('pyramid_mako')
508 config.include('pyramid_beaker')
510 config.include('pyramid_beaker')
509 config.include('rhodecode.lib.rc_cache')
511 config.include('rhodecode.lib.rc_cache')
510
512
511 add_events_routes(config)
513 add_events_routes(config)
512
514
513 return config
515 return config
514
516
515
517
516 def bootstrap_request(**kwargs):
518 def bootstrap_request(**kwargs):
517 import pyramid.testing
519 import pyramid.testing
518
520
519 class TestRequest(pyramid.testing.DummyRequest):
521 class TestRequest(pyramid.testing.DummyRequest):
520 application_url = kwargs.pop('application_url', 'http://example.com')
522 application_url = kwargs.pop('application_url', 'http://example.com')
521 host = kwargs.pop('host', 'example.com:80')
523 host = kwargs.pop('host', 'example.com:80')
522 domain = kwargs.pop('domain', 'example.com')
524 domain = kwargs.pop('domain', 'example.com')
523
525
524 def translate(self, msg):
526 def translate(self, msg):
525 return msg
527 return msg
526
528
527 def plularize(self, singular, plural, n):
529 def plularize(self, singular, plural, n):
528 return singular
530 return singular
529
531
530 def get_partial_renderer(self, tmpl_name):
532 def get_partial_renderer(self, tmpl_name):
531
533
532 from rhodecode.lib.partial_renderer import get_partial_renderer
534 from rhodecode.lib.partial_renderer import get_partial_renderer
533 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
535 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
534
536
535 _call_context = {}
537 _call_context = {}
536 @property
538 @property
537 def call_context(self):
539 def call_context(self):
538 return self._call_context
540 return self._call_context
539
541
540 class TestDummySession(pyramid.testing.DummySession):
542 class TestDummySession(pyramid.testing.DummySession):
541 def save(*arg, **kw):
543 def save(*arg, **kw):
542 pass
544 pass
543
545
544 request = TestRequest(**kwargs)
546 request = TestRequest(**kwargs)
545 request.session = TestDummySession()
547 request.session = TestDummySession()
546
548
547 return request
549 return request
548
550
General Comments 0
You need to be logged in to leave comments. Login now