##// END OF EJS Templates
ssh: call custom hooks via SSH backend
marcink -
r3637:c15c96da default
parent child Browse files
Show More
@@ -1,137 +1,147 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 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 logging
23 import logging
24 import tempfile
24 import tempfile
25 import textwrap
25 import textwrap
26 import collections
26 import collections
27 from .base import VcsServer
27 from .base import VcsServer
28 from rhodecode.model.db import RhodeCodeUi
28 from rhodecode.model.settings import VcsSettingsModel
29 from rhodecode.model.settings import VcsSettingsModel
29
30
30 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
31
32
32
33
33 class MercurialTunnelWrapper(object):
34 class MercurialTunnelWrapper(object):
34 process = None
35 process = None
35
36
36 def __init__(self, server):
37 def __init__(self, server):
37 self.server = server
38 self.server = server
38 self.stdin = sys.stdin
39 self.stdin = sys.stdin
39 self.stdout = sys.stdout
40 self.stdout = sys.stdout
40 self.hooks_env_fd, self.hooks_env_path = tempfile.mkstemp(prefix='hgrc_rhodecode_')
41 self.hooks_env_fd, self.hooks_env_path = tempfile.mkstemp(prefix='hgrc_rhodecode_')
41
42
42 def create_hooks_env(self):
43 def create_hooks_env(self):
43 repo_name = self.server.repo_name
44 repo_name = self.server.repo_name
44 hg_flags = self.config_to_hgrc(repo_name)
45 hg_flags = self.config_to_hgrc(repo_name)
45
46
46 content = textwrap.dedent(
47 content = textwrap.dedent(
47 '''
48 '''
48 # SSH hooks version=2.0.0
49 # RhodeCode SSH hooks version=2.0.0
49 [hooks]
50 pretxnchangegroup.ssh_auth=python:vcsserver.hooks.pre_push_ssh_auth
51 pretxnchangegroup.ssh=python:vcsserver.hooks.pre_push_ssh
52 changegroup.ssh=python:vcsserver.hooks.post_push_ssh
53
54 preoutgoing.ssh=python:vcsserver.hooks.pre_pull_ssh
55 outgoing.ssh=python:vcsserver.hooks.post_pull_ssh
56
57 # Custom Config version=2.0.0
58 {custom}
50 {custom}
59 '''
51 '''
60 ).format(custom='\n'.join(hg_flags))
52 ).format(custom='\n'.join(hg_flags))
61
53
62 root = self.server.get_root_store()
54 root = self.server.get_root_store()
63 hgrc_custom = os.path.join(root, repo_name, '.hg', 'hgrc_rhodecode')
55 hgrc_custom = os.path.join(root, repo_name, '.hg', 'hgrc_rhodecode')
64 hgrc_main = os.path.join(root, repo_name, '.hg', 'hgrc')
56 hgrc_main = os.path.join(root, repo_name, '.hg', 'hgrc')
65
57
66 # cleanup custom hgrc file
58 # cleanup custom hgrc file
67 if os.path.isfile(hgrc_custom):
59 if os.path.isfile(hgrc_custom):
68 with open(hgrc_custom, 'wb') as f:
60 with open(hgrc_custom, 'wb') as f:
69 f.write('')
61 f.write('')
70 log.debug('Cleanup custom hgrc file under %s', hgrc_custom)
62 log.debug('Cleanup custom hgrc file under %s', hgrc_custom)
71
63
72 # write temp
64 # write temp
73 with os.fdopen(self.hooks_env_fd, 'w') as hooks_env_file:
65 with os.fdopen(self.hooks_env_fd, 'w') as hooks_env_file:
74 hooks_env_file.write(content)
66 hooks_env_file.write(content)
75
67
76 return self.hooks_env_path
68 return self.hooks_env_path
77
69
78 def remove_configs(self):
70 def remove_configs(self):
79 os.remove(self.hooks_env_path)
71 os.remove(self.hooks_env_path)
80
72
81 def command(self, hgrc_path):
73 def command(self, hgrc_path):
82 root = self.server.get_root_store()
74 root = self.server.get_root_store()
83
75
84 command = (
76 command = (
85 "cd {root}; HGRCPATH={hgrc} {hg_path} -R {root}{repo_name} "
77 "cd {root}; HGRCPATH={hgrc} {hg_path} -R {root}{repo_name} "
86 "serve --stdio".format(
78 "serve --stdio".format(
87 root=root, hg_path=self.server.hg_path,
79 root=root, hg_path=self.server.hg_path,
88 repo_name=self.server.repo_name, hgrc=hgrc_path))
80 repo_name=self.server.repo_name, hgrc=hgrc_path))
89 log.debug("Final CMD: %s", command)
81 log.debug("Final CMD: %s", command)
90 return command
82 return command
91
83
92 def run(self, extras):
84 def run(self, extras):
93 # at this point we cannot tell, we do further ACL checks
85 # at this point we cannot tell, we do further ACL checks
94 # inside the hooks
86 # inside the hooks
95 action = '?'
87 action = '?'
96 # permissions are check via `pre_push_ssh_auth` hook
88 # permissions are check via `pre_push_ssh_auth` hook
97 self.server.update_environment(action=action, extras=extras)
89 self.server.update_environment(action=action, extras=extras)
98 custom_hgrc_file = self.create_hooks_env()
90 custom_hgrc_file = self.create_hooks_env()
99
91
100 try:
92 try:
101 return os.system(self.command(custom_hgrc_file))
93 return os.system(self.command(custom_hgrc_file))
102 finally:
94 finally:
103 self.remove_configs()
95 self.remove_configs()
104
96
105
97
106 class MercurialServer(VcsServer):
98 class MercurialServer(VcsServer):
107 backend = 'hg'
99 backend = 'hg'
108 cli_flags = ['phases', 'largefiles', 'extensions', 'experimental']
100 cli_flags = ['phases', 'largefiles', 'extensions', 'experimental', 'hooks']
109
101
110 def __init__(self, store, ini_path, repo_name, user, user_permissions, config, env):
102 def __init__(self, store, ini_path, repo_name, user, user_permissions, config, env):
111 super(MercurialServer, self).__init__(user, user_permissions, config, env)
103 super(MercurialServer, self).__init__(user, user_permissions, config, env)
112
104
113 self.store = store
105 self.store = store
114 self.ini_path = ini_path
106 self.ini_path = ini_path
115 self.repo_name = repo_name
107 self.repo_name = repo_name
116 self._path = self.hg_path = config.get('app:main', 'ssh.executable.hg')
108 self._path = self.hg_path = config.get('app:main', 'ssh.executable.hg')
117 self.tunnel = MercurialTunnelWrapper(server=self)
109 self.tunnel = MercurialTunnelWrapper(server=self)
118
110
119 def config_to_hgrc(self, repo_name):
111 def config_to_hgrc(self, repo_name):
120 ui_sections = collections.defaultdict(list)
112 ui_sections = collections.defaultdict(list)
121 ui = VcsSettingsModel(repo=repo_name).get_ui_settings(section=None, key=None)
113 ui = VcsSettingsModel(repo=repo_name).get_ui_settings(section=None, key=None)
122
114
115 # write default hooks
116 default_hooks = [
117 ('pretxnchangegroup.ssh_auth', 'python:vcsserver.hooks.pre_push_ssh_auth'),
118 ('pretxnchangegroup.ssh', 'python:vcsserver.hooks.pre_push_ssh'),
119 ('changegroup.ssh', 'python:vcsserver.hooks.post_push_ssh'),
120
121 ('preoutgoing.ssh', 'python:vcsserver.hooks.pre_pull_ssh'),
122 ('outgoing.ssh', 'python:vcsserver.hooks.post_pull_ssh'),
123 ]
124
125 for k, v in default_hooks:
126 ui_sections['hooks'].append((k, v))
127
123 for entry in ui:
128 for entry in ui:
124 if not entry.active:
129 if not entry.active:
125 continue
130 continue
126 sec = entry.section
131 sec = entry.section
132 key = entry.key
127
133
128 if sec in self.cli_flags:
134 if sec in self.cli_flags:
129 ui_sections[sec].append([entry.key, entry.value])
135 # we want only custom hooks, so we skip builtins
136 if sec == 'hooks' and key in RhodeCodeUi.HOOKS_BUILTIN:
137 continue
138
139 ui_sections[sec].append([key, entry.value])
130
140
131 flags = []
141 flags = []
132 for _sec, key_val in ui_sections.items():
142 for _sec, key_val in ui_sections.items():
133 flags.append(' ')
143 flags.append(' ')
134 flags.append('[{}]'.format(_sec))
144 flags.append('[{}]'.format(_sec))
135 for key, val in key_val:
145 for key, val in key_val:
136 flags.append('{}= {}'.format(key, val))
146 flags.append('{}= {}'.format(key, val))
137 return flags
147 return flags
@@ -1,5012 +1,5021 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import string
28 import string
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import warnings
32 import warnings
33 import ipaddress
33 import ipaddress
34 import functools
34 import functools
35 import traceback
35 import traceback
36 import collections
36 import collections
37
37
38 from sqlalchemy import (
38 from sqlalchemy import (
39 or_, and_, not_, func, TypeDecorator, event,
39 or_, and_, not_, func, TypeDecorator, event,
40 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
41 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
42 Text, Float, PickleType)
42 Text, Float, PickleType)
43 from sqlalchemy.sql.expression import true, false, case
43 from sqlalchemy.sql.expression import true, false, case
44 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
44 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
45 from sqlalchemy.orm import (
45 from sqlalchemy.orm import (
46 relationship, joinedload, class_mapper, validates, aliased)
46 relationship, joinedload, class_mapper, validates, aliased)
47 from sqlalchemy.ext.declarative import declared_attr
47 from sqlalchemy.ext.declarative import declared_attr
48 from sqlalchemy.ext.hybrid import hybrid_property
48 from sqlalchemy.ext.hybrid import hybrid_property
49 from sqlalchemy.exc import IntegrityError # pragma: no cover
49 from sqlalchemy.exc import IntegrityError # pragma: no cover
50 from sqlalchemy.dialects.mysql import LONGTEXT
50 from sqlalchemy.dialects.mysql import LONGTEXT
51 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from zope.cachedescriptors.property import Lazy as LazyProperty
52 from pyramid import compat
52 from pyramid import compat
53 from pyramid.threadlocal import get_current_request
53 from pyramid.threadlocal import get_current_request
54 from webhelpers.text import collapse, remove_formatting
54 from webhelpers.text import collapse, remove_formatting
55
55
56 from rhodecode.translation import _
56 from rhodecode.translation import _
57 from rhodecode.lib.vcs import get_vcs_instance
57 from rhodecode.lib.vcs import get_vcs_instance
58 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
58 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
59 from rhodecode.lib.utils2 import (
59 from rhodecode.lib.utils2 import (
60 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
60 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
61 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
61 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
62 glob2re, StrictAttributeDict, cleaned_uri)
62 glob2re, StrictAttributeDict, cleaned_uri)
63 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
63 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
64 JsonRaw
64 JsonRaw
65 from rhodecode.lib.ext_json import json
65 from rhodecode.lib.ext_json import json
66 from rhodecode.lib.caching_query import FromCache
66 from rhodecode.lib.caching_query import FromCache
67 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
67 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
68 from rhodecode.lib.encrypt2 import Encryptor
68 from rhodecode.lib.encrypt2 import Encryptor
69 from rhodecode.model.meta import Base, Session
69 from rhodecode.model.meta import Base, Session
70
70
71 URL_SEP = '/'
71 URL_SEP = '/'
72 log = logging.getLogger(__name__)
72 log = logging.getLogger(__name__)
73
73
74 # =============================================================================
74 # =============================================================================
75 # BASE CLASSES
75 # BASE CLASSES
76 # =============================================================================
76 # =============================================================================
77
77
78 # this is propagated from .ini file rhodecode.encrypted_values.secret or
78 # this is propagated from .ini file rhodecode.encrypted_values.secret or
79 # beaker.session.secret if first is not set.
79 # beaker.session.secret if first is not set.
80 # and initialized at environment.py
80 # and initialized at environment.py
81 ENCRYPTION_KEY = None
81 ENCRYPTION_KEY = None
82
82
83 # used to sort permissions by types, '#' used here is not allowed to be in
83 # used to sort permissions by types, '#' used here is not allowed to be in
84 # usernames, and it's very early in sorted string.printable table.
84 # usernames, and it's very early in sorted string.printable table.
85 PERMISSION_TYPE_SORT = {
85 PERMISSION_TYPE_SORT = {
86 'admin': '####',
86 'admin': '####',
87 'write': '###',
87 'write': '###',
88 'read': '##',
88 'read': '##',
89 'none': '#',
89 'none': '#',
90 }
90 }
91
91
92
92
93 def display_user_sort(obj):
93 def display_user_sort(obj):
94 """
94 """
95 Sort function used to sort permissions in .permissions() function of
95 Sort function used to sort permissions in .permissions() function of
96 Repository, RepoGroup, UserGroup. Also it put the default user in front
96 Repository, RepoGroup, UserGroup. Also it put the default user in front
97 of all other resources
97 of all other resources
98 """
98 """
99
99
100 if obj.username == User.DEFAULT_USER:
100 if obj.username == User.DEFAULT_USER:
101 return '#####'
101 return '#####'
102 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
102 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
103 return prefix + obj.username
103 return prefix + obj.username
104
104
105
105
106 def display_user_group_sort(obj):
106 def display_user_group_sort(obj):
107 """
107 """
108 Sort function used to sort permissions in .permissions() function of
108 Sort function used to sort permissions in .permissions() function of
109 Repository, RepoGroup, UserGroup. Also it put the default user in front
109 Repository, RepoGroup, UserGroup. Also it put the default user in front
110 of all other resources
110 of all other resources
111 """
111 """
112
112
113 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
113 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
114 return prefix + obj.users_group_name
114 return prefix + obj.users_group_name
115
115
116
116
117 def _hash_key(k):
117 def _hash_key(k):
118 return sha1_safe(k)
118 return sha1_safe(k)
119
119
120
120
121 def in_filter_generator(qry, items, limit=500):
121 def in_filter_generator(qry, items, limit=500):
122 """
122 """
123 Splits IN() into multiple with OR
123 Splits IN() into multiple with OR
124 e.g.::
124 e.g.::
125 cnt = Repository.query().filter(
125 cnt = Repository.query().filter(
126 or_(
126 or_(
127 *in_filter_generator(Repository.repo_id, range(100000))
127 *in_filter_generator(Repository.repo_id, range(100000))
128 )).count()
128 )).count()
129 """
129 """
130 if not items:
130 if not items:
131 # empty list will cause empty query which might cause security issues
131 # empty list will cause empty query which might cause security issues
132 # this can lead to hidden unpleasant results
132 # this can lead to hidden unpleasant results
133 items = [-1]
133 items = [-1]
134
134
135 parts = []
135 parts = []
136 for chunk in xrange(0, len(items), limit):
136 for chunk in xrange(0, len(items), limit):
137 parts.append(
137 parts.append(
138 qry.in_(items[chunk: chunk + limit])
138 qry.in_(items[chunk: chunk + limit])
139 )
139 )
140
140
141 return parts
141 return parts
142
142
143
143
144 base_table_args = {
144 base_table_args = {
145 'extend_existing': True,
145 'extend_existing': True,
146 'mysql_engine': 'InnoDB',
146 'mysql_engine': 'InnoDB',
147 'mysql_charset': 'utf8',
147 'mysql_charset': 'utf8',
148 'sqlite_autoincrement': True
148 'sqlite_autoincrement': True
149 }
149 }
150
150
151
151
152 class EncryptedTextValue(TypeDecorator):
152 class EncryptedTextValue(TypeDecorator):
153 """
153 """
154 Special column for encrypted long text data, use like::
154 Special column for encrypted long text data, use like::
155
155
156 value = Column("encrypted_value", EncryptedValue(), nullable=False)
156 value = Column("encrypted_value", EncryptedValue(), nullable=False)
157
157
158 This column is intelligent so if value is in unencrypted form it return
158 This column is intelligent so if value is in unencrypted form it return
159 unencrypted form, but on save it always encrypts
159 unencrypted form, but on save it always encrypts
160 """
160 """
161 impl = Text
161 impl = Text
162
162
163 def process_bind_param(self, value, dialect):
163 def process_bind_param(self, value, dialect):
164 """
164 """
165 Setter for storing value
165 Setter for storing value
166 """
166 """
167 import rhodecode
167 import rhodecode
168 if not value:
168 if not value:
169 return value
169 return value
170
170
171 # protect against double encrypting if values is already encrypted
171 # protect against double encrypting if values is already encrypted
172 if value.startswith('enc$aes$') \
172 if value.startswith('enc$aes$') \
173 or value.startswith('enc$aes_hmac$') \
173 or value.startswith('enc$aes_hmac$') \
174 or value.startswith('enc2$'):
174 or value.startswith('enc2$'):
175 raise ValueError('value needs to be in unencrypted format, '
175 raise ValueError('value needs to be in unencrypted format, '
176 'ie. not starting with enc$ or enc2$')
176 'ie. not starting with enc$ or enc2$')
177
177
178 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
178 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
179 if algo == 'aes':
179 if algo == 'aes':
180 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
180 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
181 elif algo == 'fernet':
181 elif algo == 'fernet':
182 return Encryptor(ENCRYPTION_KEY).encrypt(value)
182 return Encryptor(ENCRYPTION_KEY).encrypt(value)
183 else:
183 else:
184 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
184 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
185
185
186 def process_result_value(self, value, dialect):
186 def process_result_value(self, value, dialect):
187 """
187 """
188 Getter for retrieving value
188 Getter for retrieving value
189 """
189 """
190
190
191 import rhodecode
191 import rhodecode
192 if not value:
192 if not value:
193 return value
193 return value
194
194
195 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
195 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
196 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
196 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
197 if algo == 'aes':
197 if algo == 'aes':
198 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
198 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
199 elif algo == 'fernet':
199 elif algo == 'fernet':
200 return Encryptor(ENCRYPTION_KEY).decrypt(value)
200 return Encryptor(ENCRYPTION_KEY).decrypt(value)
201 else:
201 else:
202 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
202 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
203 return decrypted_data
203 return decrypted_data
204
204
205
205
206 class BaseModel(object):
206 class BaseModel(object):
207 """
207 """
208 Base Model for all classes
208 Base Model for all classes
209 """
209 """
210
210
211 @classmethod
211 @classmethod
212 def _get_keys(cls):
212 def _get_keys(cls):
213 """return column names for this model """
213 """return column names for this model """
214 return class_mapper(cls).c.keys()
214 return class_mapper(cls).c.keys()
215
215
216 def get_dict(self):
216 def get_dict(self):
217 """
217 """
218 return dict with keys and values corresponding
218 return dict with keys and values corresponding
219 to this model data """
219 to this model data """
220
220
221 d = {}
221 d = {}
222 for k in self._get_keys():
222 for k in self._get_keys():
223 d[k] = getattr(self, k)
223 d[k] = getattr(self, k)
224
224
225 # also use __json__() if present to get additional fields
225 # also use __json__() if present to get additional fields
226 _json_attr = getattr(self, '__json__', None)
226 _json_attr = getattr(self, '__json__', None)
227 if _json_attr:
227 if _json_attr:
228 # update with attributes from __json__
228 # update with attributes from __json__
229 if callable(_json_attr):
229 if callable(_json_attr):
230 _json_attr = _json_attr()
230 _json_attr = _json_attr()
231 for k, val in _json_attr.iteritems():
231 for k, val in _json_attr.iteritems():
232 d[k] = val
232 d[k] = val
233 return d
233 return d
234
234
235 def get_appstruct(self):
235 def get_appstruct(self):
236 """return list with keys and values tuples corresponding
236 """return list with keys and values tuples corresponding
237 to this model data """
237 to this model data """
238
238
239 lst = []
239 lst = []
240 for k in self._get_keys():
240 for k in self._get_keys():
241 lst.append((k, getattr(self, k),))
241 lst.append((k, getattr(self, k),))
242 return lst
242 return lst
243
243
244 def populate_obj(self, populate_dict):
244 def populate_obj(self, populate_dict):
245 """populate model with data from given populate_dict"""
245 """populate model with data from given populate_dict"""
246
246
247 for k in self._get_keys():
247 for k in self._get_keys():
248 if k in populate_dict:
248 if k in populate_dict:
249 setattr(self, k, populate_dict[k])
249 setattr(self, k, populate_dict[k])
250
250
251 @classmethod
251 @classmethod
252 def query(cls):
252 def query(cls):
253 return Session().query(cls)
253 return Session().query(cls)
254
254
255 @classmethod
255 @classmethod
256 def get(cls, id_):
256 def get(cls, id_):
257 if id_:
257 if id_:
258 return cls.query().get(id_)
258 return cls.query().get(id_)
259
259
260 @classmethod
260 @classmethod
261 def get_or_404(cls, id_):
261 def get_or_404(cls, id_):
262 from pyramid.httpexceptions import HTTPNotFound
262 from pyramid.httpexceptions import HTTPNotFound
263
263
264 try:
264 try:
265 id_ = int(id_)
265 id_ = int(id_)
266 except (TypeError, ValueError):
266 except (TypeError, ValueError):
267 raise HTTPNotFound()
267 raise HTTPNotFound()
268
268
269 res = cls.query().get(id_)
269 res = cls.query().get(id_)
270 if not res:
270 if not res:
271 raise HTTPNotFound()
271 raise HTTPNotFound()
272 return res
272 return res
273
273
274 @classmethod
274 @classmethod
275 def getAll(cls):
275 def getAll(cls):
276 # deprecated and left for backward compatibility
276 # deprecated and left for backward compatibility
277 return cls.get_all()
277 return cls.get_all()
278
278
279 @classmethod
279 @classmethod
280 def get_all(cls):
280 def get_all(cls):
281 return cls.query().all()
281 return cls.query().all()
282
282
283 @classmethod
283 @classmethod
284 def delete(cls, id_):
284 def delete(cls, id_):
285 obj = cls.query().get(id_)
285 obj = cls.query().get(id_)
286 Session().delete(obj)
286 Session().delete(obj)
287
287
288 @classmethod
288 @classmethod
289 def identity_cache(cls, session, attr_name, value):
289 def identity_cache(cls, session, attr_name, value):
290 exist_in_session = []
290 exist_in_session = []
291 for (item_cls, pkey), instance in session.identity_map.items():
291 for (item_cls, pkey), instance in session.identity_map.items():
292 if cls == item_cls and getattr(instance, attr_name) == value:
292 if cls == item_cls and getattr(instance, attr_name) == value:
293 exist_in_session.append(instance)
293 exist_in_session.append(instance)
294 if exist_in_session:
294 if exist_in_session:
295 if len(exist_in_session) == 1:
295 if len(exist_in_session) == 1:
296 return exist_in_session[0]
296 return exist_in_session[0]
297 log.exception(
297 log.exception(
298 'multiple objects with attr %s and '
298 'multiple objects with attr %s and '
299 'value %s found with same name: %r',
299 'value %s found with same name: %r',
300 attr_name, value, exist_in_session)
300 attr_name, value, exist_in_session)
301
301
302 def __repr__(self):
302 def __repr__(self):
303 if hasattr(self, '__unicode__'):
303 if hasattr(self, '__unicode__'):
304 # python repr needs to return str
304 # python repr needs to return str
305 try:
305 try:
306 return safe_str(self.__unicode__())
306 return safe_str(self.__unicode__())
307 except UnicodeDecodeError:
307 except UnicodeDecodeError:
308 pass
308 pass
309 return '<DB:%s>' % (self.__class__.__name__)
309 return '<DB:%s>' % (self.__class__.__name__)
310
310
311
311
312 class RhodeCodeSetting(Base, BaseModel):
312 class RhodeCodeSetting(Base, BaseModel):
313 __tablename__ = 'rhodecode_settings'
313 __tablename__ = 'rhodecode_settings'
314 __table_args__ = (
314 __table_args__ = (
315 UniqueConstraint('app_settings_name'),
315 UniqueConstraint('app_settings_name'),
316 base_table_args
316 base_table_args
317 )
317 )
318
318
319 SETTINGS_TYPES = {
319 SETTINGS_TYPES = {
320 'str': safe_str,
320 'str': safe_str,
321 'int': safe_int,
321 'int': safe_int,
322 'unicode': safe_unicode,
322 'unicode': safe_unicode,
323 'bool': str2bool,
323 'bool': str2bool,
324 'list': functools.partial(aslist, sep=',')
324 'list': functools.partial(aslist, sep=',')
325 }
325 }
326 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
326 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
327 GLOBAL_CONF_KEY = 'app_settings'
327 GLOBAL_CONF_KEY = 'app_settings'
328
328
329 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
329 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
330 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
330 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
331 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
331 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
332 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
332 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
333
333
334 def __init__(self, key='', val='', type='unicode'):
334 def __init__(self, key='', val='', type='unicode'):
335 self.app_settings_name = key
335 self.app_settings_name = key
336 self.app_settings_type = type
336 self.app_settings_type = type
337 self.app_settings_value = val
337 self.app_settings_value = val
338
338
339 @validates('_app_settings_value')
339 @validates('_app_settings_value')
340 def validate_settings_value(self, key, val):
340 def validate_settings_value(self, key, val):
341 assert type(val) == unicode
341 assert type(val) == unicode
342 return val
342 return val
343
343
344 @hybrid_property
344 @hybrid_property
345 def app_settings_value(self):
345 def app_settings_value(self):
346 v = self._app_settings_value
346 v = self._app_settings_value
347 _type = self.app_settings_type
347 _type = self.app_settings_type
348 if _type:
348 if _type:
349 _type = self.app_settings_type.split('.')[0]
349 _type = self.app_settings_type.split('.')[0]
350 # decode the encrypted value
350 # decode the encrypted value
351 if 'encrypted' in self.app_settings_type:
351 if 'encrypted' in self.app_settings_type:
352 cipher = EncryptedTextValue()
352 cipher = EncryptedTextValue()
353 v = safe_unicode(cipher.process_result_value(v, None))
353 v = safe_unicode(cipher.process_result_value(v, None))
354
354
355 converter = self.SETTINGS_TYPES.get(_type) or \
355 converter = self.SETTINGS_TYPES.get(_type) or \
356 self.SETTINGS_TYPES['unicode']
356 self.SETTINGS_TYPES['unicode']
357 return converter(v)
357 return converter(v)
358
358
359 @app_settings_value.setter
359 @app_settings_value.setter
360 def app_settings_value(self, val):
360 def app_settings_value(self, val):
361 """
361 """
362 Setter that will always make sure we use unicode in app_settings_value
362 Setter that will always make sure we use unicode in app_settings_value
363
363
364 :param val:
364 :param val:
365 """
365 """
366 val = safe_unicode(val)
366 val = safe_unicode(val)
367 # encode the encrypted value
367 # encode the encrypted value
368 if 'encrypted' in self.app_settings_type:
368 if 'encrypted' in self.app_settings_type:
369 cipher = EncryptedTextValue()
369 cipher = EncryptedTextValue()
370 val = safe_unicode(cipher.process_bind_param(val, None))
370 val = safe_unicode(cipher.process_bind_param(val, None))
371 self._app_settings_value = val
371 self._app_settings_value = val
372
372
373 @hybrid_property
373 @hybrid_property
374 def app_settings_type(self):
374 def app_settings_type(self):
375 return self._app_settings_type
375 return self._app_settings_type
376
376
377 @app_settings_type.setter
377 @app_settings_type.setter
378 def app_settings_type(self, val):
378 def app_settings_type(self, val):
379 if val.split('.')[0] not in self.SETTINGS_TYPES:
379 if val.split('.')[0] not in self.SETTINGS_TYPES:
380 raise Exception('type must be one of %s got %s'
380 raise Exception('type must be one of %s got %s'
381 % (self.SETTINGS_TYPES.keys(), val))
381 % (self.SETTINGS_TYPES.keys(), val))
382 self._app_settings_type = val
382 self._app_settings_type = val
383
383
384 @classmethod
384 @classmethod
385 def get_by_prefix(cls, prefix):
385 def get_by_prefix(cls, prefix):
386 return RhodeCodeSetting.query()\
386 return RhodeCodeSetting.query()\
387 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
387 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
388 .all()
388 .all()
389
389
390 def __unicode__(self):
390 def __unicode__(self):
391 return u"<%s('%s:%s[%s]')>" % (
391 return u"<%s('%s:%s[%s]')>" % (
392 self.__class__.__name__,
392 self.__class__.__name__,
393 self.app_settings_name, self.app_settings_value,
393 self.app_settings_name, self.app_settings_value,
394 self.app_settings_type
394 self.app_settings_type
395 )
395 )
396
396
397
397
398 class RhodeCodeUi(Base, BaseModel):
398 class RhodeCodeUi(Base, BaseModel):
399 __tablename__ = 'rhodecode_ui'
399 __tablename__ = 'rhodecode_ui'
400 __table_args__ = (
400 __table_args__ = (
401 UniqueConstraint('ui_key'),
401 UniqueConstraint('ui_key'),
402 base_table_args
402 base_table_args
403 )
403 )
404
404
405 HOOK_REPO_SIZE = 'changegroup.repo_size'
405 HOOK_REPO_SIZE = 'changegroup.repo_size'
406 # HG
406 # HG
407 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
407 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
408 HOOK_PULL = 'outgoing.pull_logger'
408 HOOK_PULL = 'outgoing.pull_logger'
409 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
409 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
410 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
410 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
411 HOOK_PUSH = 'changegroup.push_logger'
411 HOOK_PUSH = 'changegroup.push_logger'
412 HOOK_PUSH_KEY = 'pushkey.key_push'
412 HOOK_PUSH_KEY = 'pushkey.key_push'
413
413
414 HOOKS_BUILTIN = [
415 HOOK_PRE_PULL,
416 HOOK_PULL,
417 HOOK_PRE_PUSH,
418 HOOK_PRETX_PUSH,
419 HOOK_PUSH,
420 HOOK_PUSH_KEY,
421 ]
422
414 # TODO: johbo: Unify way how hooks are configured for git and hg,
423 # TODO: johbo: Unify way how hooks are configured for git and hg,
415 # git part is currently hardcoded.
424 # git part is currently hardcoded.
416
425
417 # SVN PATTERNS
426 # SVN PATTERNS
418 SVN_BRANCH_ID = 'vcs_svn_branch'
427 SVN_BRANCH_ID = 'vcs_svn_branch'
419 SVN_TAG_ID = 'vcs_svn_tag'
428 SVN_TAG_ID = 'vcs_svn_tag'
420
429
421 ui_id = Column(
430 ui_id = Column(
422 "ui_id", Integer(), nullable=False, unique=True, default=None,
431 "ui_id", Integer(), nullable=False, unique=True, default=None,
423 primary_key=True)
432 primary_key=True)
424 ui_section = Column(
433 ui_section = Column(
425 "ui_section", String(255), nullable=True, unique=None, default=None)
434 "ui_section", String(255), nullable=True, unique=None, default=None)
426 ui_key = Column(
435 ui_key = Column(
427 "ui_key", String(255), nullable=True, unique=None, default=None)
436 "ui_key", String(255), nullable=True, unique=None, default=None)
428 ui_value = Column(
437 ui_value = Column(
429 "ui_value", String(255), nullable=True, unique=None, default=None)
438 "ui_value", String(255), nullable=True, unique=None, default=None)
430 ui_active = Column(
439 ui_active = Column(
431 "ui_active", Boolean(), nullable=True, unique=None, default=True)
440 "ui_active", Boolean(), nullable=True, unique=None, default=True)
432
441
433 def __repr__(self):
442 def __repr__(self):
434 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
443 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
435 self.ui_key, self.ui_value)
444 self.ui_key, self.ui_value)
436
445
437
446
438 class RepoRhodeCodeSetting(Base, BaseModel):
447 class RepoRhodeCodeSetting(Base, BaseModel):
439 __tablename__ = 'repo_rhodecode_settings'
448 __tablename__ = 'repo_rhodecode_settings'
440 __table_args__ = (
449 __table_args__ = (
441 UniqueConstraint(
450 UniqueConstraint(
442 'app_settings_name', 'repository_id',
451 'app_settings_name', 'repository_id',
443 name='uq_repo_rhodecode_setting_name_repo_id'),
452 name='uq_repo_rhodecode_setting_name_repo_id'),
444 base_table_args
453 base_table_args
445 )
454 )
446
455
447 repository_id = Column(
456 repository_id = Column(
448 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
457 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
449 nullable=False)
458 nullable=False)
450 app_settings_id = Column(
459 app_settings_id = Column(
451 "app_settings_id", Integer(), nullable=False, unique=True,
460 "app_settings_id", Integer(), nullable=False, unique=True,
452 default=None, primary_key=True)
461 default=None, primary_key=True)
453 app_settings_name = Column(
462 app_settings_name = Column(
454 "app_settings_name", String(255), nullable=True, unique=None,
463 "app_settings_name", String(255), nullable=True, unique=None,
455 default=None)
464 default=None)
456 _app_settings_value = Column(
465 _app_settings_value = Column(
457 "app_settings_value", String(4096), nullable=True, unique=None,
466 "app_settings_value", String(4096), nullable=True, unique=None,
458 default=None)
467 default=None)
459 _app_settings_type = Column(
468 _app_settings_type = Column(
460 "app_settings_type", String(255), nullable=True, unique=None,
469 "app_settings_type", String(255), nullable=True, unique=None,
461 default=None)
470 default=None)
462
471
463 repository = relationship('Repository')
472 repository = relationship('Repository')
464
473
465 def __init__(self, repository_id, key='', val='', type='unicode'):
474 def __init__(self, repository_id, key='', val='', type='unicode'):
466 self.repository_id = repository_id
475 self.repository_id = repository_id
467 self.app_settings_name = key
476 self.app_settings_name = key
468 self.app_settings_type = type
477 self.app_settings_type = type
469 self.app_settings_value = val
478 self.app_settings_value = val
470
479
471 @validates('_app_settings_value')
480 @validates('_app_settings_value')
472 def validate_settings_value(self, key, val):
481 def validate_settings_value(self, key, val):
473 assert type(val) == unicode
482 assert type(val) == unicode
474 return val
483 return val
475
484
476 @hybrid_property
485 @hybrid_property
477 def app_settings_value(self):
486 def app_settings_value(self):
478 v = self._app_settings_value
487 v = self._app_settings_value
479 type_ = self.app_settings_type
488 type_ = self.app_settings_type
480 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
489 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
481 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
490 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
482 return converter(v)
491 return converter(v)
483
492
484 @app_settings_value.setter
493 @app_settings_value.setter
485 def app_settings_value(self, val):
494 def app_settings_value(self, val):
486 """
495 """
487 Setter that will always make sure we use unicode in app_settings_value
496 Setter that will always make sure we use unicode in app_settings_value
488
497
489 :param val:
498 :param val:
490 """
499 """
491 self._app_settings_value = safe_unicode(val)
500 self._app_settings_value = safe_unicode(val)
492
501
493 @hybrid_property
502 @hybrid_property
494 def app_settings_type(self):
503 def app_settings_type(self):
495 return self._app_settings_type
504 return self._app_settings_type
496
505
497 @app_settings_type.setter
506 @app_settings_type.setter
498 def app_settings_type(self, val):
507 def app_settings_type(self, val):
499 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
508 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
500 if val not in SETTINGS_TYPES:
509 if val not in SETTINGS_TYPES:
501 raise Exception('type must be one of %s got %s'
510 raise Exception('type must be one of %s got %s'
502 % (SETTINGS_TYPES.keys(), val))
511 % (SETTINGS_TYPES.keys(), val))
503 self._app_settings_type = val
512 self._app_settings_type = val
504
513
505 def __unicode__(self):
514 def __unicode__(self):
506 return u"<%s('%s:%s:%s[%s]')>" % (
515 return u"<%s('%s:%s:%s[%s]')>" % (
507 self.__class__.__name__, self.repository.repo_name,
516 self.__class__.__name__, self.repository.repo_name,
508 self.app_settings_name, self.app_settings_value,
517 self.app_settings_name, self.app_settings_value,
509 self.app_settings_type
518 self.app_settings_type
510 )
519 )
511
520
512
521
513 class RepoRhodeCodeUi(Base, BaseModel):
522 class RepoRhodeCodeUi(Base, BaseModel):
514 __tablename__ = 'repo_rhodecode_ui'
523 __tablename__ = 'repo_rhodecode_ui'
515 __table_args__ = (
524 __table_args__ = (
516 UniqueConstraint(
525 UniqueConstraint(
517 'repository_id', 'ui_section', 'ui_key',
526 'repository_id', 'ui_section', 'ui_key',
518 name='uq_repo_rhodecode_ui_repository_id_section_key'),
527 name='uq_repo_rhodecode_ui_repository_id_section_key'),
519 base_table_args
528 base_table_args
520 )
529 )
521
530
522 repository_id = Column(
531 repository_id = Column(
523 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
532 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
524 nullable=False)
533 nullable=False)
525 ui_id = Column(
534 ui_id = Column(
526 "ui_id", Integer(), nullable=False, unique=True, default=None,
535 "ui_id", Integer(), nullable=False, unique=True, default=None,
527 primary_key=True)
536 primary_key=True)
528 ui_section = Column(
537 ui_section = Column(
529 "ui_section", String(255), nullable=True, unique=None, default=None)
538 "ui_section", String(255), nullable=True, unique=None, default=None)
530 ui_key = Column(
539 ui_key = Column(
531 "ui_key", String(255), nullable=True, unique=None, default=None)
540 "ui_key", String(255), nullable=True, unique=None, default=None)
532 ui_value = Column(
541 ui_value = Column(
533 "ui_value", String(255), nullable=True, unique=None, default=None)
542 "ui_value", String(255), nullable=True, unique=None, default=None)
534 ui_active = Column(
543 ui_active = Column(
535 "ui_active", Boolean(), nullable=True, unique=None, default=True)
544 "ui_active", Boolean(), nullable=True, unique=None, default=True)
536
545
537 repository = relationship('Repository')
546 repository = relationship('Repository')
538
547
539 def __repr__(self):
548 def __repr__(self):
540 return '<%s[%s:%s]%s=>%s]>' % (
549 return '<%s[%s:%s]%s=>%s]>' % (
541 self.__class__.__name__, self.repository.repo_name,
550 self.__class__.__name__, self.repository.repo_name,
542 self.ui_section, self.ui_key, self.ui_value)
551 self.ui_section, self.ui_key, self.ui_value)
543
552
544
553
545 class User(Base, BaseModel):
554 class User(Base, BaseModel):
546 __tablename__ = 'users'
555 __tablename__ = 'users'
547 __table_args__ = (
556 __table_args__ = (
548 UniqueConstraint('username'), UniqueConstraint('email'),
557 UniqueConstraint('username'), UniqueConstraint('email'),
549 Index('u_username_idx', 'username'),
558 Index('u_username_idx', 'username'),
550 Index('u_email_idx', 'email'),
559 Index('u_email_idx', 'email'),
551 base_table_args
560 base_table_args
552 )
561 )
553
562
554 DEFAULT_USER = 'default'
563 DEFAULT_USER = 'default'
555 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
564 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
556 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
565 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
557
566
558 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
567 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
559 username = Column("username", String(255), nullable=True, unique=None, default=None)
568 username = Column("username", String(255), nullable=True, unique=None, default=None)
560 password = Column("password", String(255), nullable=True, unique=None, default=None)
569 password = Column("password", String(255), nullable=True, unique=None, default=None)
561 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
570 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
562 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
571 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
563 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
572 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
564 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
573 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
565 _email = Column("email", String(255), nullable=True, unique=None, default=None)
574 _email = Column("email", String(255), nullable=True, unique=None, default=None)
566 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
575 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
567 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
576 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
568
577
569 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
578 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
570 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
579 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
571 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
580 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
572 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
581 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
573 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
582 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
574 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
583 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
575
584
576 user_log = relationship('UserLog')
585 user_log = relationship('UserLog')
577 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
586 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
578
587
579 repositories = relationship('Repository')
588 repositories = relationship('Repository')
580 repository_groups = relationship('RepoGroup')
589 repository_groups = relationship('RepoGroup')
581 user_groups = relationship('UserGroup')
590 user_groups = relationship('UserGroup')
582
591
583 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
592 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
584 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
593 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
585
594
586 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
595 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
587 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
596 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
588 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
597 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
589
598
590 group_member = relationship('UserGroupMember', cascade='all')
599 group_member = relationship('UserGroupMember', cascade='all')
591
600
592 notifications = relationship('UserNotification', cascade='all')
601 notifications = relationship('UserNotification', cascade='all')
593 # notifications assigned to this user
602 # notifications assigned to this user
594 user_created_notifications = relationship('Notification', cascade='all')
603 user_created_notifications = relationship('Notification', cascade='all')
595 # comments created by this user
604 # comments created by this user
596 user_comments = relationship('ChangesetComment', cascade='all')
605 user_comments = relationship('ChangesetComment', cascade='all')
597 # user profile extra info
606 # user profile extra info
598 user_emails = relationship('UserEmailMap', cascade='all')
607 user_emails = relationship('UserEmailMap', cascade='all')
599 user_ip_map = relationship('UserIpMap', cascade='all')
608 user_ip_map = relationship('UserIpMap', cascade='all')
600 user_auth_tokens = relationship('UserApiKeys', cascade='all')
609 user_auth_tokens = relationship('UserApiKeys', cascade='all')
601 user_ssh_keys = relationship('UserSshKeys', cascade='all')
610 user_ssh_keys = relationship('UserSshKeys', cascade='all')
602
611
603 # gists
612 # gists
604 user_gists = relationship('Gist', cascade='all')
613 user_gists = relationship('Gist', cascade='all')
605 # user pull requests
614 # user pull requests
606 user_pull_requests = relationship('PullRequest', cascade='all')
615 user_pull_requests = relationship('PullRequest', cascade='all')
607 # external identities
616 # external identities
608 extenal_identities = relationship(
617 extenal_identities = relationship(
609 'ExternalIdentity',
618 'ExternalIdentity',
610 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
619 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
611 cascade='all')
620 cascade='all')
612 # review rules
621 # review rules
613 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
622 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
614
623
615 def __unicode__(self):
624 def __unicode__(self):
616 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
625 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
617 self.user_id, self.username)
626 self.user_id, self.username)
618
627
619 @hybrid_property
628 @hybrid_property
620 def email(self):
629 def email(self):
621 return self._email
630 return self._email
622
631
623 @email.setter
632 @email.setter
624 def email(self, val):
633 def email(self, val):
625 self._email = val.lower() if val else None
634 self._email = val.lower() if val else None
626
635
627 @hybrid_property
636 @hybrid_property
628 def first_name(self):
637 def first_name(self):
629 from rhodecode.lib import helpers as h
638 from rhodecode.lib import helpers as h
630 if self.name:
639 if self.name:
631 return h.escape(self.name)
640 return h.escape(self.name)
632 return self.name
641 return self.name
633
642
634 @hybrid_property
643 @hybrid_property
635 def last_name(self):
644 def last_name(self):
636 from rhodecode.lib import helpers as h
645 from rhodecode.lib import helpers as h
637 if self.lastname:
646 if self.lastname:
638 return h.escape(self.lastname)
647 return h.escape(self.lastname)
639 return self.lastname
648 return self.lastname
640
649
641 @hybrid_property
650 @hybrid_property
642 def api_key(self):
651 def api_key(self):
643 """
652 """
644 Fetch if exist an auth-token with role ALL connected to this user
653 Fetch if exist an auth-token with role ALL connected to this user
645 """
654 """
646 user_auth_token = UserApiKeys.query()\
655 user_auth_token = UserApiKeys.query()\
647 .filter(UserApiKeys.user_id == self.user_id)\
656 .filter(UserApiKeys.user_id == self.user_id)\
648 .filter(or_(UserApiKeys.expires == -1,
657 .filter(or_(UserApiKeys.expires == -1,
649 UserApiKeys.expires >= time.time()))\
658 UserApiKeys.expires >= time.time()))\
650 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
659 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
651 if user_auth_token:
660 if user_auth_token:
652 user_auth_token = user_auth_token.api_key
661 user_auth_token = user_auth_token.api_key
653
662
654 return user_auth_token
663 return user_auth_token
655
664
656 @api_key.setter
665 @api_key.setter
657 def api_key(self, val):
666 def api_key(self, val):
658 # don't allow to set API key this is deprecated for now
667 # don't allow to set API key this is deprecated for now
659 self._api_key = None
668 self._api_key = None
660
669
661 @property
670 @property
662 def reviewer_pull_requests(self):
671 def reviewer_pull_requests(self):
663 return PullRequestReviewers.query() \
672 return PullRequestReviewers.query() \
664 .options(joinedload(PullRequestReviewers.pull_request)) \
673 .options(joinedload(PullRequestReviewers.pull_request)) \
665 .filter(PullRequestReviewers.user_id == self.user_id) \
674 .filter(PullRequestReviewers.user_id == self.user_id) \
666 .all()
675 .all()
667
676
668 @property
677 @property
669 def firstname(self):
678 def firstname(self):
670 # alias for future
679 # alias for future
671 return self.name
680 return self.name
672
681
673 @property
682 @property
674 def emails(self):
683 def emails(self):
675 other = UserEmailMap.query()\
684 other = UserEmailMap.query()\
676 .filter(UserEmailMap.user == self) \
685 .filter(UserEmailMap.user == self) \
677 .order_by(UserEmailMap.email_id.asc()) \
686 .order_by(UserEmailMap.email_id.asc()) \
678 .all()
687 .all()
679 return [self.email] + [x.email for x in other]
688 return [self.email] + [x.email for x in other]
680
689
681 @property
690 @property
682 def auth_tokens(self):
691 def auth_tokens(self):
683 auth_tokens = self.get_auth_tokens()
692 auth_tokens = self.get_auth_tokens()
684 return [x.api_key for x in auth_tokens]
693 return [x.api_key for x in auth_tokens]
685
694
686 def get_auth_tokens(self):
695 def get_auth_tokens(self):
687 return UserApiKeys.query()\
696 return UserApiKeys.query()\
688 .filter(UserApiKeys.user == self)\
697 .filter(UserApiKeys.user == self)\
689 .order_by(UserApiKeys.user_api_key_id.asc())\
698 .order_by(UserApiKeys.user_api_key_id.asc())\
690 .all()
699 .all()
691
700
692 @LazyProperty
701 @LazyProperty
693 def feed_token(self):
702 def feed_token(self):
694 return self.get_feed_token()
703 return self.get_feed_token()
695
704
696 def get_feed_token(self, cache=True):
705 def get_feed_token(self, cache=True):
697 feed_tokens = UserApiKeys.query()\
706 feed_tokens = UserApiKeys.query()\
698 .filter(UserApiKeys.user == self)\
707 .filter(UserApiKeys.user == self)\
699 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
708 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
700 if cache:
709 if cache:
701 feed_tokens = feed_tokens.options(
710 feed_tokens = feed_tokens.options(
702 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
711 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
703
712
704 feed_tokens = feed_tokens.all()
713 feed_tokens = feed_tokens.all()
705 if feed_tokens:
714 if feed_tokens:
706 return feed_tokens[0].api_key
715 return feed_tokens[0].api_key
707 return 'NO_FEED_TOKEN_AVAILABLE'
716 return 'NO_FEED_TOKEN_AVAILABLE'
708
717
709 @classmethod
718 @classmethod
710 def get(cls, user_id, cache=False):
719 def get(cls, user_id, cache=False):
711 if not user_id:
720 if not user_id:
712 return
721 return
713
722
714 user = cls.query()
723 user = cls.query()
715 if cache:
724 if cache:
716 user = user.options(
725 user = user.options(
717 FromCache("sql_cache_short", "get_users_%s" % user_id))
726 FromCache("sql_cache_short", "get_users_%s" % user_id))
718 return user.get(user_id)
727 return user.get(user_id)
719
728
720 @classmethod
729 @classmethod
721 def extra_valid_auth_tokens(cls, user, role=None):
730 def extra_valid_auth_tokens(cls, user, role=None):
722 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
731 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
723 .filter(or_(UserApiKeys.expires == -1,
732 .filter(or_(UserApiKeys.expires == -1,
724 UserApiKeys.expires >= time.time()))
733 UserApiKeys.expires >= time.time()))
725 if role:
734 if role:
726 tokens = tokens.filter(or_(UserApiKeys.role == role,
735 tokens = tokens.filter(or_(UserApiKeys.role == role,
727 UserApiKeys.role == UserApiKeys.ROLE_ALL))
736 UserApiKeys.role == UserApiKeys.ROLE_ALL))
728 return tokens.all()
737 return tokens.all()
729
738
730 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
739 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
731 from rhodecode.lib import auth
740 from rhodecode.lib import auth
732
741
733 log.debug('Trying to authenticate user: %s via auth-token, '
742 log.debug('Trying to authenticate user: %s via auth-token, '
734 'and roles: %s', self, roles)
743 'and roles: %s', self, roles)
735
744
736 if not auth_token:
745 if not auth_token:
737 return False
746 return False
738
747
739 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
748 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
740 tokens_q = UserApiKeys.query()\
749 tokens_q = UserApiKeys.query()\
741 .filter(UserApiKeys.user_id == self.user_id)\
750 .filter(UserApiKeys.user_id == self.user_id)\
742 .filter(or_(UserApiKeys.expires == -1,
751 .filter(or_(UserApiKeys.expires == -1,
743 UserApiKeys.expires >= time.time()))
752 UserApiKeys.expires >= time.time()))
744
753
745 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
754 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
746
755
747 crypto_backend = auth.crypto_backend()
756 crypto_backend = auth.crypto_backend()
748 enc_token_map = {}
757 enc_token_map = {}
749 plain_token_map = {}
758 plain_token_map = {}
750 for token in tokens_q:
759 for token in tokens_q:
751 if token.api_key.startswith(crypto_backend.ENC_PREF):
760 if token.api_key.startswith(crypto_backend.ENC_PREF):
752 enc_token_map[token.api_key] = token
761 enc_token_map[token.api_key] = token
753 else:
762 else:
754 plain_token_map[token.api_key] = token
763 plain_token_map[token.api_key] = token
755 log.debug(
764 log.debug(
756 'Found %s plain and %s encrypted user tokens to check for authentication',
765 'Found %s plain and %s encrypted user tokens to check for authentication',
757 len(plain_token_map), len(enc_token_map))
766 len(plain_token_map), len(enc_token_map))
758
767
759 # plain token match comes first
768 # plain token match comes first
760 match = plain_token_map.get(auth_token)
769 match = plain_token_map.get(auth_token)
761
770
762 # check encrypted tokens now
771 # check encrypted tokens now
763 if not match:
772 if not match:
764 for token_hash, token in enc_token_map.items():
773 for token_hash, token in enc_token_map.items():
765 # NOTE(marcink): this is expensive to calculate, but most secure
774 # NOTE(marcink): this is expensive to calculate, but most secure
766 if crypto_backend.hash_check(auth_token, token_hash):
775 if crypto_backend.hash_check(auth_token, token_hash):
767 match = token
776 match = token
768 break
777 break
769
778
770 if match:
779 if match:
771 log.debug('Found matching token %s', match)
780 log.debug('Found matching token %s', match)
772 if match.repo_id:
781 if match.repo_id:
773 log.debug('Found scope, checking for scope match of token %s', match)
782 log.debug('Found scope, checking for scope match of token %s', match)
774 if match.repo_id == scope_repo_id:
783 if match.repo_id == scope_repo_id:
775 return True
784 return True
776 else:
785 else:
777 log.debug(
786 log.debug(
778 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
787 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
779 'and calling scope is:%s, skipping further checks',
788 'and calling scope is:%s, skipping further checks',
780 match.repo, scope_repo_id)
789 match.repo, scope_repo_id)
781 return False
790 return False
782 else:
791 else:
783 return True
792 return True
784
793
785 return False
794 return False
786
795
787 @property
796 @property
788 def ip_addresses(self):
797 def ip_addresses(self):
789 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
798 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
790 return [x.ip_addr for x in ret]
799 return [x.ip_addr for x in ret]
791
800
792 @property
801 @property
793 def username_and_name(self):
802 def username_and_name(self):
794 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
803 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
795
804
796 @property
805 @property
797 def username_or_name_or_email(self):
806 def username_or_name_or_email(self):
798 full_name = self.full_name if self.full_name is not ' ' else None
807 full_name = self.full_name if self.full_name is not ' ' else None
799 return self.username or full_name or self.email
808 return self.username or full_name or self.email
800
809
801 @property
810 @property
802 def full_name(self):
811 def full_name(self):
803 return '%s %s' % (self.first_name, self.last_name)
812 return '%s %s' % (self.first_name, self.last_name)
804
813
805 @property
814 @property
806 def full_name_or_username(self):
815 def full_name_or_username(self):
807 return ('%s %s' % (self.first_name, self.last_name)
816 return ('%s %s' % (self.first_name, self.last_name)
808 if (self.first_name and self.last_name) else self.username)
817 if (self.first_name and self.last_name) else self.username)
809
818
810 @property
819 @property
811 def full_contact(self):
820 def full_contact(self):
812 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
821 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
813
822
814 @property
823 @property
815 def short_contact(self):
824 def short_contact(self):
816 return '%s %s' % (self.first_name, self.last_name)
825 return '%s %s' % (self.first_name, self.last_name)
817
826
818 @property
827 @property
819 def is_admin(self):
828 def is_admin(self):
820 return self.admin
829 return self.admin
821
830
822 def AuthUser(self, **kwargs):
831 def AuthUser(self, **kwargs):
823 """
832 """
824 Returns instance of AuthUser for this user
833 Returns instance of AuthUser for this user
825 """
834 """
826 from rhodecode.lib.auth import AuthUser
835 from rhodecode.lib.auth import AuthUser
827 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
836 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
828
837
829 @hybrid_property
838 @hybrid_property
830 def user_data(self):
839 def user_data(self):
831 if not self._user_data:
840 if not self._user_data:
832 return {}
841 return {}
833
842
834 try:
843 try:
835 return json.loads(self._user_data)
844 return json.loads(self._user_data)
836 except TypeError:
845 except TypeError:
837 return {}
846 return {}
838
847
839 @user_data.setter
848 @user_data.setter
840 def user_data(self, val):
849 def user_data(self, val):
841 if not isinstance(val, dict):
850 if not isinstance(val, dict):
842 raise Exception('user_data must be dict, got %s' % type(val))
851 raise Exception('user_data must be dict, got %s' % type(val))
843 try:
852 try:
844 self._user_data = json.dumps(val)
853 self._user_data = json.dumps(val)
845 except Exception:
854 except Exception:
846 log.error(traceback.format_exc())
855 log.error(traceback.format_exc())
847
856
848 @classmethod
857 @classmethod
849 def get_by_username(cls, username, case_insensitive=False,
858 def get_by_username(cls, username, case_insensitive=False,
850 cache=False, identity_cache=False):
859 cache=False, identity_cache=False):
851 session = Session()
860 session = Session()
852
861
853 if case_insensitive:
862 if case_insensitive:
854 q = cls.query().filter(
863 q = cls.query().filter(
855 func.lower(cls.username) == func.lower(username))
864 func.lower(cls.username) == func.lower(username))
856 else:
865 else:
857 q = cls.query().filter(cls.username == username)
866 q = cls.query().filter(cls.username == username)
858
867
859 if cache:
868 if cache:
860 if identity_cache:
869 if identity_cache:
861 val = cls.identity_cache(session, 'username', username)
870 val = cls.identity_cache(session, 'username', username)
862 if val:
871 if val:
863 return val
872 return val
864 else:
873 else:
865 cache_key = "get_user_by_name_%s" % _hash_key(username)
874 cache_key = "get_user_by_name_%s" % _hash_key(username)
866 q = q.options(
875 q = q.options(
867 FromCache("sql_cache_short", cache_key))
876 FromCache("sql_cache_short", cache_key))
868
877
869 return q.scalar()
878 return q.scalar()
870
879
871 @classmethod
880 @classmethod
872 def get_by_auth_token(cls, auth_token, cache=False):
881 def get_by_auth_token(cls, auth_token, cache=False):
873 q = UserApiKeys.query()\
882 q = UserApiKeys.query()\
874 .filter(UserApiKeys.api_key == auth_token)\
883 .filter(UserApiKeys.api_key == auth_token)\
875 .filter(or_(UserApiKeys.expires == -1,
884 .filter(or_(UserApiKeys.expires == -1,
876 UserApiKeys.expires >= time.time()))
885 UserApiKeys.expires >= time.time()))
877 if cache:
886 if cache:
878 q = q.options(
887 q = q.options(
879 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
888 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
880
889
881 match = q.first()
890 match = q.first()
882 if match:
891 if match:
883 return match.user
892 return match.user
884
893
885 @classmethod
894 @classmethod
886 def get_by_email(cls, email, case_insensitive=False, cache=False):
895 def get_by_email(cls, email, case_insensitive=False, cache=False):
887
896
888 if case_insensitive:
897 if case_insensitive:
889 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
898 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
890
899
891 else:
900 else:
892 q = cls.query().filter(cls.email == email)
901 q = cls.query().filter(cls.email == email)
893
902
894 email_key = _hash_key(email)
903 email_key = _hash_key(email)
895 if cache:
904 if cache:
896 q = q.options(
905 q = q.options(
897 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
906 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
898
907
899 ret = q.scalar()
908 ret = q.scalar()
900 if ret is None:
909 if ret is None:
901 q = UserEmailMap.query()
910 q = UserEmailMap.query()
902 # try fetching in alternate email map
911 # try fetching in alternate email map
903 if case_insensitive:
912 if case_insensitive:
904 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
913 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
905 else:
914 else:
906 q = q.filter(UserEmailMap.email == email)
915 q = q.filter(UserEmailMap.email == email)
907 q = q.options(joinedload(UserEmailMap.user))
916 q = q.options(joinedload(UserEmailMap.user))
908 if cache:
917 if cache:
909 q = q.options(
918 q = q.options(
910 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
919 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
911 ret = getattr(q.scalar(), 'user', None)
920 ret = getattr(q.scalar(), 'user', None)
912
921
913 return ret
922 return ret
914
923
915 @classmethod
924 @classmethod
916 def get_from_cs_author(cls, author):
925 def get_from_cs_author(cls, author):
917 """
926 """
918 Tries to get User objects out of commit author string
927 Tries to get User objects out of commit author string
919
928
920 :param author:
929 :param author:
921 """
930 """
922 from rhodecode.lib.helpers import email, author_name
931 from rhodecode.lib.helpers import email, author_name
923 # Valid email in the attribute passed, see if they're in the system
932 # Valid email in the attribute passed, see if they're in the system
924 _email = email(author)
933 _email = email(author)
925 if _email:
934 if _email:
926 user = cls.get_by_email(_email, case_insensitive=True)
935 user = cls.get_by_email(_email, case_insensitive=True)
927 if user:
936 if user:
928 return user
937 return user
929 # Maybe we can match by username?
938 # Maybe we can match by username?
930 _author = author_name(author)
939 _author = author_name(author)
931 user = cls.get_by_username(_author, case_insensitive=True)
940 user = cls.get_by_username(_author, case_insensitive=True)
932 if user:
941 if user:
933 return user
942 return user
934
943
935 def update_userdata(self, **kwargs):
944 def update_userdata(self, **kwargs):
936 usr = self
945 usr = self
937 old = usr.user_data
946 old = usr.user_data
938 old.update(**kwargs)
947 old.update(**kwargs)
939 usr.user_data = old
948 usr.user_data = old
940 Session().add(usr)
949 Session().add(usr)
941 log.debug('updated userdata with ', kwargs)
950 log.debug('updated userdata with ', kwargs)
942
951
943 def update_lastlogin(self):
952 def update_lastlogin(self):
944 """Update user lastlogin"""
953 """Update user lastlogin"""
945 self.last_login = datetime.datetime.now()
954 self.last_login = datetime.datetime.now()
946 Session().add(self)
955 Session().add(self)
947 log.debug('updated user %s lastlogin', self.username)
956 log.debug('updated user %s lastlogin', self.username)
948
957
949 def update_password(self, new_password):
958 def update_password(self, new_password):
950 from rhodecode.lib.auth import get_crypt_password
959 from rhodecode.lib.auth import get_crypt_password
951
960
952 self.password = get_crypt_password(new_password)
961 self.password = get_crypt_password(new_password)
953 Session().add(self)
962 Session().add(self)
954
963
955 @classmethod
964 @classmethod
956 def get_first_super_admin(cls):
965 def get_first_super_admin(cls):
957 user = User.query()\
966 user = User.query()\
958 .filter(User.admin == true()) \
967 .filter(User.admin == true()) \
959 .order_by(User.user_id.asc()) \
968 .order_by(User.user_id.asc()) \
960 .first()
969 .first()
961
970
962 if user is None:
971 if user is None:
963 raise Exception('FATAL: Missing administrative account!')
972 raise Exception('FATAL: Missing administrative account!')
964 return user
973 return user
965
974
966 @classmethod
975 @classmethod
967 def get_all_super_admins(cls, only_active=False):
976 def get_all_super_admins(cls, only_active=False):
968 """
977 """
969 Returns all admin accounts sorted by username
978 Returns all admin accounts sorted by username
970 """
979 """
971 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
980 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
972 if only_active:
981 if only_active:
973 qry = qry.filter(User.active == true())
982 qry = qry.filter(User.active == true())
974 return qry.all()
983 return qry.all()
975
984
976 @classmethod
985 @classmethod
977 def get_default_user(cls, cache=False, refresh=False):
986 def get_default_user(cls, cache=False, refresh=False):
978 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
987 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
979 if user is None:
988 if user is None:
980 raise Exception('FATAL: Missing default account!')
989 raise Exception('FATAL: Missing default account!')
981 if refresh:
990 if refresh:
982 # The default user might be based on outdated state which
991 # The default user might be based on outdated state which
983 # has been loaded from the cache.
992 # has been loaded from the cache.
984 # A call to refresh() ensures that the
993 # A call to refresh() ensures that the
985 # latest state from the database is used.
994 # latest state from the database is used.
986 Session().refresh(user)
995 Session().refresh(user)
987 return user
996 return user
988
997
989 def _get_default_perms(self, user, suffix=''):
998 def _get_default_perms(self, user, suffix=''):
990 from rhodecode.model.permission import PermissionModel
999 from rhodecode.model.permission import PermissionModel
991 return PermissionModel().get_default_perms(user.user_perms, suffix)
1000 return PermissionModel().get_default_perms(user.user_perms, suffix)
992
1001
993 def get_default_perms(self, suffix=''):
1002 def get_default_perms(self, suffix=''):
994 return self._get_default_perms(self, suffix)
1003 return self._get_default_perms(self, suffix)
995
1004
996 def get_api_data(self, include_secrets=False, details='full'):
1005 def get_api_data(self, include_secrets=False, details='full'):
997 """
1006 """
998 Common function for generating user related data for API
1007 Common function for generating user related data for API
999
1008
1000 :param include_secrets: By default secrets in the API data will be replaced
1009 :param include_secrets: By default secrets in the API data will be replaced
1001 by a placeholder value to prevent exposing this data by accident. In case
1010 by a placeholder value to prevent exposing this data by accident. In case
1002 this data shall be exposed, set this flag to ``True``.
1011 this data shall be exposed, set this flag to ``True``.
1003
1012
1004 :param details: details can be 'basic|full' basic gives only a subset of
1013 :param details: details can be 'basic|full' basic gives only a subset of
1005 the available user information that includes user_id, name and emails.
1014 the available user information that includes user_id, name and emails.
1006 """
1015 """
1007 user = self
1016 user = self
1008 user_data = self.user_data
1017 user_data = self.user_data
1009 data = {
1018 data = {
1010 'user_id': user.user_id,
1019 'user_id': user.user_id,
1011 'username': user.username,
1020 'username': user.username,
1012 'firstname': user.name,
1021 'firstname': user.name,
1013 'lastname': user.lastname,
1022 'lastname': user.lastname,
1014 'email': user.email,
1023 'email': user.email,
1015 'emails': user.emails,
1024 'emails': user.emails,
1016 }
1025 }
1017 if details == 'basic':
1026 if details == 'basic':
1018 return data
1027 return data
1019
1028
1020 auth_token_length = 40
1029 auth_token_length = 40
1021 auth_token_replacement = '*' * auth_token_length
1030 auth_token_replacement = '*' * auth_token_length
1022
1031
1023 extras = {
1032 extras = {
1024 'auth_tokens': [auth_token_replacement],
1033 'auth_tokens': [auth_token_replacement],
1025 'active': user.active,
1034 'active': user.active,
1026 'admin': user.admin,
1035 'admin': user.admin,
1027 'extern_type': user.extern_type,
1036 'extern_type': user.extern_type,
1028 'extern_name': user.extern_name,
1037 'extern_name': user.extern_name,
1029 'last_login': user.last_login,
1038 'last_login': user.last_login,
1030 'last_activity': user.last_activity,
1039 'last_activity': user.last_activity,
1031 'ip_addresses': user.ip_addresses,
1040 'ip_addresses': user.ip_addresses,
1032 'language': user_data.get('language')
1041 'language': user_data.get('language')
1033 }
1042 }
1034 data.update(extras)
1043 data.update(extras)
1035
1044
1036 if include_secrets:
1045 if include_secrets:
1037 data['auth_tokens'] = user.auth_tokens
1046 data['auth_tokens'] = user.auth_tokens
1038 return data
1047 return data
1039
1048
1040 def __json__(self):
1049 def __json__(self):
1041 data = {
1050 data = {
1042 'full_name': self.full_name,
1051 'full_name': self.full_name,
1043 'full_name_or_username': self.full_name_or_username,
1052 'full_name_or_username': self.full_name_or_username,
1044 'short_contact': self.short_contact,
1053 'short_contact': self.short_contact,
1045 'full_contact': self.full_contact,
1054 'full_contact': self.full_contact,
1046 }
1055 }
1047 data.update(self.get_api_data())
1056 data.update(self.get_api_data())
1048 return data
1057 return data
1049
1058
1050
1059
1051 class UserApiKeys(Base, BaseModel):
1060 class UserApiKeys(Base, BaseModel):
1052 __tablename__ = 'user_api_keys'
1061 __tablename__ = 'user_api_keys'
1053 __table_args__ = (
1062 __table_args__ = (
1054 Index('uak_api_key_idx', 'api_key', unique=True),
1063 Index('uak_api_key_idx', 'api_key', unique=True),
1055 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1064 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1056 base_table_args
1065 base_table_args
1057 )
1066 )
1058 __mapper_args__ = {}
1067 __mapper_args__ = {}
1059
1068
1060 # ApiKey role
1069 # ApiKey role
1061 ROLE_ALL = 'token_role_all'
1070 ROLE_ALL = 'token_role_all'
1062 ROLE_HTTP = 'token_role_http'
1071 ROLE_HTTP = 'token_role_http'
1063 ROLE_VCS = 'token_role_vcs'
1072 ROLE_VCS = 'token_role_vcs'
1064 ROLE_API = 'token_role_api'
1073 ROLE_API = 'token_role_api'
1065 ROLE_FEED = 'token_role_feed'
1074 ROLE_FEED = 'token_role_feed'
1066 ROLE_PASSWORD_RESET = 'token_password_reset'
1075 ROLE_PASSWORD_RESET = 'token_password_reset'
1067
1076
1068 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1077 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1069
1078
1070 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1079 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1071 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1080 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1072 api_key = Column("api_key", String(255), nullable=False, unique=True)
1081 api_key = Column("api_key", String(255), nullable=False, unique=True)
1073 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1082 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1074 expires = Column('expires', Float(53), nullable=False)
1083 expires = Column('expires', Float(53), nullable=False)
1075 role = Column('role', String(255), nullable=True)
1084 role = Column('role', String(255), nullable=True)
1076 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1085 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1077
1086
1078 # scope columns
1087 # scope columns
1079 repo_id = Column(
1088 repo_id = Column(
1080 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1089 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1081 nullable=True, unique=None, default=None)
1090 nullable=True, unique=None, default=None)
1082 repo = relationship('Repository', lazy='joined')
1091 repo = relationship('Repository', lazy='joined')
1083
1092
1084 repo_group_id = Column(
1093 repo_group_id = Column(
1085 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1094 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1086 nullable=True, unique=None, default=None)
1095 nullable=True, unique=None, default=None)
1087 repo_group = relationship('RepoGroup', lazy='joined')
1096 repo_group = relationship('RepoGroup', lazy='joined')
1088
1097
1089 user = relationship('User', lazy='joined')
1098 user = relationship('User', lazy='joined')
1090
1099
1091 def __unicode__(self):
1100 def __unicode__(self):
1092 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1101 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1093
1102
1094 def __json__(self):
1103 def __json__(self):
1095 data = {
1104 data = {
1096 'auth_token': self.api_key,
1105 'auth_token': self.api_key,
1097 'role': self.role,
1106 'role': self.role,
1098 'scope': self.scope_humanized,
1107 'scope': self.scope_humanized,
1099 'expired': self.expired
1108 'expired': self.expired
1100 }
1109 }
1101 return data
1110 return data
1102
1111
1103 def get_api_data(self, include_secrets=False):
1112 def get_api_data(self, include_secrets=False):
1104 data = self.__json__()
1113 data = self.__json__()
1105 if include_secrets:
1114 if include_secrets:
1106 return data
1115 return data
1107 else:
1116 else:
1108 data['auth_token'] = self.token_obfuscated
1117 data['auth_token'] = self.token_obfuscated
1109 return data
1118 return data
1110
1119
1111 @hybrid_property
1120 @hybrid_property
1112 def description_safe(self):
1121 def description_safe(self):
1113 from rhodecode.lib import helpers as h
1122 from rhodecode.lib import helpers as h
1114 return h.escape(self.description)
1123 return h.escape(self.description)
1115
1124
1116 @property
1125 @property
1117 def expired(self):
1126 def expired(self):
1118 if self.expires == -1:
1127 if self.expires == -1:
1119 return False
1128 return False
1120 return time.time() > self.expires
1129 return time.time() > self.expires
1121
1130
1122 @classmethod
1131 @classmethod
1123 def _get_role_name(cls, role):
1132 def _get_role_name(cls, role):
1124 return {
1133 return {
1125 cls.ROLE_ALL: _('all'),
1134 cls.ROLE_ALL: _('all'),
1126 cls.ROLE_HTTP: _('http/web interface'),
1135 cls.ROLE_HTTP: _('http/web interface'),
1127 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1136 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1128 cls.ROLE_API: _('api calls'),
1137 cls.ROLE_API: _('api calls'),
1129 cls.ROLE_FEED: _('feed access'),
1138 cls.ROLE_FEED: _('feed access'),
1130 }.get(role, role)
1139 }.get(role, role)
1131
1140
1132 @property
1141 @property
1133 def role_humanized(self):
1142 def role_humanized(self):
1134 return self._get_role_name(self.role)
1143 return self._get_role_name(self.role)
1135
1144
1136 def _get_scope(self):
1145 def _get_scope(self):
1137 if self.repo:
1146 if self.repo:
1138 return 'Repository: {}'.format(self.repo.repo_name)
1147 return 'Repository: {}'.format(self.repo.repo_name)
1139 if self.repo_group:
1148 if self.repo_group:
1140 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1149 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1141 return 'Global'
1150 return 'Global'
1142
1151
1143 @property
1152 @property
1144 def scope_humanized(self):
1153 def scope_humanized(self):
1145 return self._get_scope()
1154 return self._get_scope()
1146
1155
1147 @property
1156 @property
1148 def token_obfuscated(self):
1157 def token_obfuscated(self):
1149 if self.api_key:
1158 if self.api_key:
1150 return self.api_key[:4] + "****"
1159 return self.api_key[:4] + "****"
1151
1160
1152
1161
1153 class UserEmailMap(Base, BaseModel):
1162 class UserEmailMap(Base, BaseModel):
1154 __tablename__ = 'user_email_map'
1163 __tablename__ = 'user_email_map'
1155 __table_args__ = (
1164 __table_args__ = (
1156 Index('uem_email_idx', 'email'),
1165 Index('uem_email_idx', 'email'),
1157 UniqueConstraint('email'),
1166 UniqueConstraint('email'),
1158 base_table_args
1167 base_table_args
1159 )
1168 )
1160 __mapper_args__ = {}
1169 __mapper_args__ = {}
1161
1170
1162 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1171 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1163 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1172 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1164 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1173 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1165 user = relationship('User', lazy='joined')
1174 user = relationship('User', lazy='joined')
1166
1175
1167 @validates('_email')
1176 @validates('_email')
1168 def validate_email(self, key, email):
1177 def validate_email(self, key, email):
1169 # check if this email is not main one
1178 # check if this email is not main one
1170 main_email = Session().query(User).filter(User.email == email).scalar()
1179 main_email = Session().query(User).filter(User.email == email).scalar()
1171 if main_email is not None:
1180 if main_email is not None:
1172 raise AttributeError('email %s is present is user table' % email)
1181 raise AttributeError('email %s is present is user table' % email)
1173 return email
1182 return email
1174
1183
1175 @hybrid_property
1184 @hybrid_property
1176 def email(self):
1185 def email(self):
1177 return self._email
1186 return self._email
1178
1187
1179 @email.setter
1188 @email.setter
1180 def email(self, val):
1189 def email(self, val):
1181 self._email = val.lower() if val else None
1190 self._email = val.lower() if val else None
1182
1191
1183
1192
1184 class UserIpMap(Base, BaseModel):
1193 class UserIpMap(Base, BaseModel):
1185 __tablename__ = 'user_ip_map'
1194 __tablename__ = 'user_ip_map'
1186 __table_args__ = (
1195 __table_args__ = (
1187 UniqueConstraint('user_id', 'ip_addr'),
1196 UniqueConstraint('user_id', 'ip_addr'),
1188 base_table_args
1197 base_table_args
1189 )
1198 )
1190 __mapper_args__ = {}
1199 __mapper_args__ = {}
1191
1200
1192 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1201 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1193 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1202 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1194 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1203 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1195 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1204 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1196 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1205 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1197 user = relationship('User', lazy='joined')
1206 user = relationship('User', lazy='joined')
1198
1207
1199 @hybrid_property
1208 @hybrid_property
1200 def description_safe(self):
1209 def description_safe(self):
1201 from rhodecode.lib import helpers as h
1210 from rhodecode.lib import helpers as h
1202 return h.escape(self.description)
1211 return h.escape(self.description)
1203
1212
1204 @classmethod
1213 @classmethod
1205 def _get_ip_range(cls, ip_addr):
1214 def _get_ip_range(cls, ip_addr):
1206 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1215 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1207 return [str(net.network_address), str(net.broadcast_address)]
1216 return [str(net.network_address), str(net.broadcast_address)]
1208
1217
1209 def __json__(self):
1218 def __json__(self):
1210 return {
1219 return {
1211 'ip_addr': self.ip_addr,
1220 'ip_addr': self.ip_addr,
1212 'ip_range': self._get_ip_range(self.ip_addr),
1221 'ip_range': self._get_ip_range(self.ip_addr),
1213 }
1222 }
1214
1223
1215 def __unicode__(self):
1224 def __unicode__(self):
1216 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1225 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1217 self.user_id, self.ip_addr)
1226 self.user_id, self.ip_addr)
1218
1227
1219
1228
1220 class UserSshKeys(Base, BaseModel):
1229 class UserSshKeys(Base, BaseModel):
1221 __tablename__ = 'user_ssh_keys'
1230 __tablename__ = 'user_ssh_keys'
1222 __table_args__ = (
1231 __table_args__ = (
1223 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1232 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1224
1233
1225 UniqueConstraint('ssh_key_fingerprint'),
1234 UniqueConstraint('ssh_key_fingerprint'),
1226
1235
1227 base_table_args
1236 base_table_args
1228 )
1237 )
1229 __mapper_args__ = {}
1238 __mapper_args__ = {}
1230
1239
1231 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1240 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1232 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1241 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1233 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1242 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1234
1243
1235 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1244 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1236
1245
1237 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1246 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1238 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1247 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1239 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1248 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1240
1249
1241 user = relationship('User', lazy='joined')
1250 user = relationship('User', lazy='joined')
1242
1251
1243 def __json__(self):
1252 def __json__(self):
1244 data = {
1253 data = {
1245 'ssh_fingerprint': self.ssh_key_fingerprint,
1254 'ssh_fingerprint': self.ssh_key_fingerprint,
1246 'description': self.description,
1255 'description': self.description,
1247 'created_on': self.created_on
1256 'created_on': self.created_on
1248 }
1257 }
1249 return data
1258 return data
1250
1259
1251 def get_api_data(self):
1260 def get_api_data(self):
1252 data = self.__json__()
1261 data = self.__json__()
1253 return data
1262 return data
1254
1263
1255
1264
1256 class UserLog(Base, BaseModel):
1265 class UserLog(Base, BaseModel):
1257 __tablename__ = 'user_logs'
1266 __tablename__ = 'user_logs'
1258 __table_args__ = (
1267 __table_args__ = (
1259 base_table_args,
1268 base_table_args,
1260 )
1269 )
1261
1270
1262 VERSION_1 = 'v1'
1271 VERSION_1 = 'v1'
1263 VERSION_2 = 'v2'
1272 VERSION_2 = 'v2'
1264 VERSIONS = [VERSION_1, VERSION_2]
1273 VERSIONS = [VERSION_1, VERSION_2]
1265
1274
1266 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1275 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1267 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1276 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1268 username = Column("username", String(255), nullable=True, unique=None, default=None)
1277 username = Column("username", String(255), nullable=True, unique=None, default=None)
1269 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1278 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1270 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1279 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1271 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1280 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1272 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1281 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1273 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1282 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1274
1283
1275 version = Column("version", String(255), nullable=True, default=VERSION_1)
1284 version = Column("version", String(255), nullable=True, default=VERSION_1)
1276 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1285 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1277 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1286 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1278
1287
1279 def __unicode__(self):
1288 def __unicode__(self):
1280 return u"<%s('id:%s:%s')>" % (
1289 return u"<%s('id:%s:%s')>" % (
1281 self.__class__.__name__, self.repository_name, self.action)
1290 self.__class__.__name__, self.repository_name, self.action)
1282
1291
1283 def __json__(self):
1292 def __json__(self):
1284 return {
1293 return {
1285 'user_id': self.user_id,
1294 'user_id': self.user_id,
1286 'username': self.username,
1295 'username': self.username,
1287 'repository_id': self.repository_id,
1296 'repository_id': self.repository_id,
1288 'repository_name': self.repository_name,
1297 'repository_name': self.repository_name,
1289 'user_ip': self.user_ip,
1298 'user_ip': self.user_ip,
1290 'action_date': self.action_date,
1299 'action_date': self.action_date,
1291 'action': self.action,
1300 'action': self.action,
1292 }
1301 }
1293
1302
1294 @hybrid_property
1303 @hybrid_property
1295 def entry_id(self):
1304 def entry_id(self):
1296 return self.user_log_id
1305 return self.user_log_id
1297
1306
1298 @property
1307 @property
1299 def action_as_day(self):
1308 def action_as_day(self):
1300 return datetime.date(*self.action_date.timetuple()[:3])
1309 return datetime.date(*self.action_date.timetuple()[:3])
1301
1310
1302 user = relationship('User')
1311 user = relationship('User')
1303 repository = relationship('Repository', cascade='')
1312 repository = relationship('Repository', cascade='')
1304
1313
1305
1314
1306 class UserGroup(Base, BaseModel):
1315 class UserGroup(Base, BaseModel):
1307 __tablename__ = 'users_groups'
1316 __tablename__ = 'users_groups'
1308 __table_args__ = (
1317 __table_args__ = (
1309 base_table_args,
1318 base_table_args,
1310 )
1319 )
1311
1320
1312 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1321 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1313 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1322 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1314 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1323 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1315 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1324 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1316 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1325 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1317 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1326 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1318 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1327 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1319 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1328 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1320
1329
1321 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1330 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1322 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1331 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1323 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1332 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1324 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1333 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1325 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1334 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1326 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1335 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1327
1336
1328 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1337 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1329 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1338 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1330
1339
1331 @classmethod
1340 @classmethod
1332 def _load_group_data(cls, column):
1341 def _load_group_data(cls, column):
1333 if not column:
1342 if not column:
1334 return {}
1343 return {}
1335
1344
1336 try:
1345 try:
1337 return json.loads(column) or {}
1346 return json.loads(column) or {}
1338 except TypeError:
1347 except TypeError:
1339 return {}
1348 return {}
1340
1349
1341 @hybrid_property
1350 @hybrid_property
1342 def description_safe(self):
1351 def description_safe(self):
1343 from rhodecode.lib import helpers as h
1352 from rhodecode.lib import helpers as h
1344 return h.escape(self.user_group_description)
1353 return h.escape(self.user_group_description)
1345
1354
1346 @hybrid_property
1355 @hybrid_property
1347 def group_data(self):
1356 def group_data(self):
1348 return self._load_group_data(self._group_data)
1357 return self._load_group_data(self._group_data)
1349
1358
1350 @group_data.expression
1359 @group_data.expression
1351 def group_data(self, **kwargs):
1360 def group_data(self, **kwargs):
1352 return self._group_data
1361 return self._group_data
1353
1362
1354 @group_data.setter
1363 @group_data.setter
1355 def group_data(self, val):
1364 def group_data(self, val):
1356 try:
1365 try:
1357 self._group_data = json.dumps(val)
1366 self._group_data = json.dumps(val)
1358 except Exception:
1367 except Exception:
1359 log.error(traceback.format_exc())
1368 log.error(traceback.format_exc())
1360
1369
1361 @classmethod
1370 @classmethod
1362 def _load_sync(cls, group_data):
1371 def _load_sync(cls, group_data):
1363 if group_data:
1372 if group_data:
1364 return group_data.get('extern_type')
1373 return group_data.get('extern_type')
1365
1374
1366 @property
1375 @property
1367 def sync(self):
1376 def sync(self):
1368 return self._load_sync(self.group_data)
1377 return self._load_sync(self.group_data)
1369
1378
1370 def __unicode__(self):
1379 def __unicode__(self):
1371 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1380 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1372 self.users_group_id,
1381 self.users_group_id,
1373 self.users_group_name)
1382 self.users_group_name)
1374
1383
1375 @classmethod
1384 @classmethod
1376 def get_by_group_name(cls, group_name, cache=False,
1385 def get_by_group_name(cls, group_name, cache=False,
1377 case_insensitive=False):
1386 case_insensitive=False):
1378 if case_insensitive:
1387 if case_insensitive:
1379 q = cls.query().filter(func.lower(cls.users_group_name) ==
1388 q = cls.query().filter(func.lower(cls.users_group_name) ==
1380 func.lower(group_name))
1389 func.lower(group_name))
1381
1390
1382 else:
1391 else:
1383 q = cls.query().filter(cls.users_group_name == group_name)
1392 q = cls.query().filter(cls.users_group_name == group_name)
1384 if cache:
1393 if cache:
1385 q = q.options(
1394 q = q.options(
1386 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1395 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1387 return q.scalar()
1396 return q.scalar()
1388
1397
1389 @classmethod
1398 @classmethod
1390 def get(cls, user_group_id, cache=False):
1399 def get(cls, user_group_id, cache=False):
1391 if not user_group_id:
1400 if not user_group_id:
1392 return
1401 return
1393
1402
1394 user_group = cls.query()
1403 user_group = cls.query()
1395 if cache:
1404 if cache:
1396 user_group = user_group.options(
1405 user_group = user_group.options(
1397 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1406 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1398 return user_group.get(user_group_id)
1407 return user_group.get(user_group_id)
1399
1408
1400 def permissions(self, with_admins=True, with_owner=True,
1409 def permissions(self, with_admins=True, with_owner=True,
1401 expand_from_user_groups=False):
1410 expand_from_user_groups=False):
1402 """
1411 """
1403 Permissions for user groups
1412 Permissions for user groups
1404 """
1413 """
1405 _admin_perm = 'usergroup.admin'
1414 _admin_perm = 'usergroup.admin'
1406
1415
1407 owner_row = []
1416 owner_row = []
1408 if with_owner:
1417 if with_owner:
1409 usr = AttributeDict(self.user.get_dict())
1418 usr = AttributeDict(self.user.get_dict())
1410 usr.owner_row = True
1419 usr.owner_row = True
1411 usr.permission = _admin_perm
1420 usr.permission = _admin_perm
1412 owner_row.append(usr)
1421 owner_row.append(usr)
1413
1422
1414 super_admin_ids = []
1423 super_admin_ids = []
1415 super_admin_rows = []
1424 super_admin_rows = []
1416 if with_admins:
1425 if with_admins:
1417 for usr in User.get_all_super_admins():
1426 for usr in User.get_all_super_admins():
1418 super_admin_ids.append(usr.user_id)
1427 super_admin_ids.append(usr.user_id)
1419 # if this admin is also owner, don't double the record
1428 # if this admin is also owner, don't double the record
1420 if usr.user_id == owner_row[0].user_id:
1429 if usr.user_id == owner_row[0].user_id:
1421 owner_row[0].admin_row = True
1430 owner_row[0].admin_row = True
1422 else:
1431 else:
1423 usr = AttributeDict(usr.get_dict())
1432 usr = AttributeDict(usr.get_dict())
1424 usr.admin_row = True
1433 usr.admin_row = True
1425 usr.permission = _admin_perm
1434 usr.permission = _admin_perm
1426 super_admin_rows.append(usr)
1435 super_admin_rows.append(usr)
1427
1436
1428 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1437 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1429 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1438 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1430 joinedload(UserUserGroupToPerm.user),
1439 joinedload(UserUserGroupToPerm.user),
1431 joinedload(UserUserGroupToPerm.permission),)
1440 joinedload(UserUserGroupToPerm.permission),)
1432
1441
1433 # get owners and admins and permissions. We do a trick of re-writing
1442 # get owners and admins and permissions. We do a trick of re-writing
1434 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1443 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1435 # has a global reference and changing one object propagates to all
1444 # has a global reference and changing one object propagates to all
1436 # others. This means if admin is also an owner admin_row that change
1445 # others. This means if admin is also an owner admin_row that change
1437 # would propagate to both objects
1446 # would propagate to both objects
1438 perm_rows = []
1447 perm_rows = []
1439 for _usr in q.all():
1448 for _usr in q.all():
1440 usr = AttributeDict(_usr.user.get_dict())
1449 usr = AttributeDict(_usr.user.get_dict())
1441 # if this user is also owner/admin, mark as duplicate record
1450 # if this user is also owner/admin, mark as duplicate record
1442 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1451 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1443 usr.duplicate_perm = True
1452 usr.duplicate_perm = True
1444 usr.permission = _usr.permission.permission_name
1453 usr.permission = _usr.permission.permission_name
1445 perm_rows.append(usr)
1454 perm_rows.append(usr)
1446
1455
1447 # filter the perm rows by 'default' first and then sort them by
1456 # filter the perm rows by 'default' first and then sort them by
1448 # admin,write,read,none permissions sorted again alphabetically in
1457 # admin,write,read,none permissions sorted again alphabetically in
1449 # each group
1458 # each group
1450 perm_rows = sorted(perm_rows, key=display_user_sort)
1459 perm_rows = sorted(perm_rows, key=display_user_sort)
1451
1460
1452 user_groups_rows = []
1461 user_groups_rows = []
1453 if expand_from_user_groups:
1462 if expand_from_user_groups:
1454 for ug in self.permission_user_groups(with_members=True):
1463 for ug in self.permission_user_groups(with_members=True):
1455 for user_data in ug.members:
1464 for user_data in ug.members:
1456 user_groups_rows.append(user_data)
1465 user_groups_rows.append(user_data)
1457
1466
1458 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1467 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1459
1468
1460 def permission_user_groups(self, with_members=False):
1469 def permission_user_groups(self, with_members=False):
1461 q = UserGroupUserGroupToPerm.query()\
1470 q = UserGroupUserGroupToPerm.query()\
1462 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1471 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1463 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1472 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1464 joinedload(UserGroupUserGroupToPerm.target_user_group),
1473 joinedload(UserGroupUserGroupToPerm.target_user_group),
1465 joinedload(UserGroupUserGroupToPerm.permission),)
1474 joinedload(UserGroupUserGroupToPerm.permission),)
1466
1475
1467 perm_rows = []
1476 perm_rows = []
1468 for _user_group in q.all():
1477 for _user_group in q.all():
1469 entry = AttributeDict(_user_group.user_group.get_dict())
1478 entry = AttributeDict(_user_group.user_group.get_dict())
1470 entry.permission = _user_group.permission.permission_name
1479 entry.permission = _user_group.permission.permission_name
1471 if with_members:
1480 if with_members:
1472 entry.members = [x.user.get_dict()
1481 entry.members = [x.user.get_dict()
1473 for x in _user_group.user_group.members]
1482 for x in _user_group.user_group.members]
1474 perm_rows.append(entry)
1483 perm_rows.append(entry)
1475
1484
1476 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1485 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1477 return perm_rows
1486 return perm_rows
1478
1487
1479 def _get_default_perms(self, user_group, suffix=''):
1488 def _get_default_perms(self, user_group, suffix=''):
1480 from rhodecode.model.permission import PermissionModel
1489 from rhodecode.model.permission import PermissionModel
1481 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1490 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1482
1491
1483 def get_default_perms(self, suffix=''):
1492 def get_default_perms(self, suffix=''):
1484 return self._get_default_perms(self, suffix)
1493 return self._get_default_perms(self, suffix)
1485
1494
1486 def get_api_data(self, with_group_members=True, include_secrets=False):
1495 def get_api_data(self, with_group_members=True, include_secrets=False):
1487 """
1496 """
1488 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1497 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1489 basically forwarded.
1498 basically forwarded.
1490
1499
1491 """
1500 """
1492 user_group = self
1501 user_group = self
1493 data = {
1502 data = {
1494 'users_group_id': user_group.users_group_id,
1503 'users_group_id': user_group.users_group_id,
1495 'group_name': user_group.users_group_name,
1504 'group_name': user_group.users_group_name,
1496 'group_description': user_group.user_group_description,
1505 'group_description': user_group.user_group_description,
1497 'active': user_group.users_group_active,
1506 'active': user_group.users_group_active,
1498 'owner': user_group.user.username,
1507 'owner': user_group.user.username,
1499 'sync': user_group.sync,
1508 'sync': user_group.sync,
1500 'owner_email': user_group.user.email,
1509 'owner_email': user_group.user.email,
1501 }
1510 }
1502
1511
1503 if with_group_members:
1512 if with_group_members:
1504 users = []
1513 users = []
1505 for user in user_group.members:
1514 for user in user_group.members:
1506 user = user.user
1515 user = user.user
1507 users.append(user.get_api_data(include_secrets=include_secrets))
1516 users.append(user.get_api_data(include_secrets=include_secrets))
1508 data['users'] = users
1517 data['users'] = users
1509
1518
1510 return data
1519 return data
1511
1520
1512
1521
1513 class UserGroupMember(Base, BaseModel):
1522 class UserGroupMember(Base, BaseModel):
1514 __tablename__ = 'users_groups_members'
1523 __tablename__ = 'users_groups_members'
1515 __table_args__ = (
1524 __table_args__ = (
1516 base_table_args,
1525 base_table_args,
1517 )
1526 )
1518
1527
1519 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1528 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1520 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1529 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1521 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1530 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1522
1531
1523 user = relationship('User', lazy='joined')
1532 user = relationship('User', lazy='joined')
1524 users_group = relationship('UserGroup')
1533 users_group = relationship('UserGroup')
1525
1534
1526 def __init__(self, gr_id='', u_id=''):
1535 def __init__(self, gr_id='', u_id=''):
1527 self.users_group_id = gr_id
1536 self.users_group_id = gr_id
1528 self.user_id = u_id
1537 self.user_id = u_id
1529
1538
1530
1539
1531 class RepositoryField(Base, BaseModel):
1540 class RepositoryField(Base, BaseModel):
1532 __tablename__ = 'repositories_fields'
1541 __tablename__ = 'repositories_fields'
1533 __table_args__ = (
1542 __table_args__ = (
1534 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1543 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1535 base_table_args,
1544 base_table_args,
1536 )
1545 )
1537
1546
1538 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1547 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1539
1548
1540 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1549 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1541 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1550 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1542 field_key = Column("field_key", String(250))
1551 field_key = Column("field_key", String(250))
1543 field_label = Column("field_label", String(1024), nullable=False)
1552 field_label = Column("field_label", String(1024), nullable=False)
1544 field_value = Column("field_value", String(10000), nullable=False)
1553 field_value = Column("field_value", String(10000), nullable=False)
1545 field_desc = Column("field_desc", String(1024), nullable=False)
1554 field_desc = Column("field_desc", String(1024), nullable=False)
1546 field_type = Column("field_type", String(255), nullable=False, unique=None)
1555 field_type = Column("field_type", String(255), nullable=False, unique=None)
1547 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1556 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1548
1557
1549 repository = relationship('Repository')
1558 repository = relationship('Repository')
1550
1559
1551 @property
1560 @property
1552 def field_key_prefixed(self):
1561 def field_key_prefixed(self):
1553 return 'ex_%s' % self.field_key
1562 return 'ex_%s' % self.field_key
1554
1563
1555 @classmethod
1564 @classmethod
1556 def un_prefix_key(cls, key):
1565 def un_prefix_key(cls, key):
1557 if key.startswith(cls.PREFIX):
1566 if key.startswith(cls.PREFIX):
1558 return key[len(cls.PREFIX):]
1567 return key[len(cls.PREFIX):]
1559 return key
1568 return key
1560
1569
1561 @classmethod
1570 @classmethod
1562 def get_by_key_name(cls, key, repo):
1571 def get_by_key_name(cls, key, repo):
1563 row = cls.query()\
1572 row = cls.query()\
1564 .filter(cls.repository == repo)\
1573 .filter(cls.repository == repo)\
1565 .filter(cls.field_key == key).scalar()
1574 .filter(cls.field_key == key).scalar()
1566 return row
1575 return row
1567
1576
1568
1577
1569 class Repository(Base, BaseModel):
1578 class Repository(Base, BaseModel):
1570 __tablename__ = 'repositories'
1579 __tablename__ = 'repositories'
1571 __table_args__ = (
1580 __table_args__ = (
1572 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1581 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1573 base_table_args,
1582 base_table_args,
1574 )
1583 )
1575 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1584 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1576 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1585 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1577 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1586 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1578
1587
1579 STATE_CREATED = 'repo_state_created'
1588 STATE_CREATED = 'repo_state_created'
1580 STATE_PENDING = 'repo_state_pending'
1589 STATE_PENDING = 'repo_state_pending'
1581 STATE_ERROR = 'repo_state_error'
1590 STATE_ERROR = 'repo_state_error'
1582
1591
1583 LOCK_AUTOMATIC = 'lock_auto'
1592 LOCK_AUTOMATIC = 'lock_auto'
1584 LOCK_API = 'lock_api'
1593 LOCK_API = 'lock_api'
1585 LOCK_WEB = 'lock_web'
1594 LOCK_WEB = 'lock_web'
1586 LOCK_PULL = 'lock_pull'
1595 LOCK_PULL = 'lock_pull'
1587
1596
1588 NAME_SEP = URL_SEP
1597 NAME_SEP = URL_SEP
1589
1598
1590 repo_id = Column(
1599 repo_id = Column(
1591 "repo_id", Integer(), nullable=False, unique=True, default=None,
1600 "repo_id", Integer(), nullable=False, unique=True, default=None,
1592 primary_key=True)
1601 primary_key=True)
1593 _repo_name = Column(
1602 _repo_name = Column(
1594 "repo_name", Text(), nullable=False, default=None)
1603 "repo_name", Text(), nullable=False, default=None)
1595 _repo_name_hash = Column(
1604 _repo_name_hash = Column(
1596 "repo_name_hash", String(255), nullable=False, unique=True)
1605 "repo_name_hash", String(255), nullable=False, unique=True)
1597 repo_state = Column("repo_state", String(255), nullable=True)
1606 repo_state = Column("repo_state", String(255), nullable=True)
1598
1607
1599 clone_uri = Column(
1608 clone_uri = Column(
1600 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1609 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1601 default=None)
1610 default=None)
1602 push_uri = Column(
1611 push_uri = Column(
1603 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1612 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1604 default=None)
1613 default=None)
1605 repo_type = Column(
1614 repo_type = Column(
1606 "repo_type", String(255), nullable=False, unique=False, default=None)
1615 "repo_type", String(255), nullable=False, unique=False, default=None)
1607 user_id = Column(
1616 user_id = Column(
1608 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1617 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1609 unique=False, default=None)
1618 unique=False, default=None)
1610 private = Column(
1619 private = Column(
1611 "private", Boolean(), nullable=True, unique=None, default=None)
1620 "private", Boolean(), nullable=True, unique=None, default=None)
1612 archived = Column(
1621 archived = Column(
1613 "archived", Boolean(), nullable=True, unique=None, default=None)
1622 "archived", Boolean(), nullable=True, unique=None, default=None)
1614 enable_statistics = Column(
1623 enable_statistics = Column(
1615 "statistics", Boolean(), nullable=True, unique=None, default=True)
1624 "statistics", Boolean(), nullable=True, unique=None, default=True)
1616 enable_downloads = Column(
1625 enable_downloads = Column(
1617 "downloads", Boolean(), nullable=True, unique=None, default=True)
1626 "downloads", Boolean(), nullable=True, unique=None, default=True)
1618 description = Column(
1627 description = Column(
1619 "description", String(10000), nullable=True, unique=None, default=None)
1628 "description", String(10000), nullable=True, unique=None, default=None)
1620 created_on = Column(
1629 created_on = Column(
1621 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1630 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1622 default=datetime.datetime.now)
1631 default=datetime.datetime.now)
1623 updated_on = Column(
1632 updated_on = Column(
1624 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1633 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1625 default=datetime.datetime.now)
1634 default=datetime.datetime.now)
1626 _landing_revision = Column(
1635 _landing_revision = Column(
1627 "landing_revision", String(255), nullable=False, unique=False,
1636 "landing_revision", String(255), nullable=False, unique=False,
1628 default=None)
1637 default=None)
1629 enable_locking = Column(
1638 enable_locking = Column(
1630 "enable_locking", Boolean(), nullable=False, unique=None,
1639 "enable_locking", Boolean(), nullable=False, unique=None,
1631 default=False)
1640 default=False)
1632 _locked = Column(
1641 _locked = Column(
1633 "locked", String(255), nullable=True, unique=False, default=None)
1642 "locked", String(255), nullable=True, unique=False, default=None)
1634 _changeset_cache = Column(
1643 _changeset_cache = Column(
1635 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1644 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1636
1645
1637 fork_id = Column(
1646 fork_id = Column(
1638 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1647 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1639 nullable=True, unique=False, default=None)
1648 nullable=True, unique=False, default=None)
1640 group_id = Column(
1649 group_id = Column(
1641 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1650 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1642 unique=False, default=None)
1651 unique=False, default=None)
1643
1652
1644 user = relationship('User', lazy='joined')
1653 user = relationship('User', lazy='joined')
1645 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1654 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1646 group = relationship('RepoGroup', lazy='joined')
1655 group = relationship('RepoGroup', lazy='joined')
1647 repo_to_perm = relationship(
1656 repo_to_perm = relationship(
1648 'UserRepoToPerm', cascade='all',
1657 'UserRepoToPerm', cascade='all',
1649 order_by='UserRepoToPerm.repo_to_perm_id')
1658 order_by='UserRepoToPerm.repo_to_perm_id')
1650 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1659 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1651 stats = relationship('Statistics', cascade='all', uselist=False)
1660 stats = relationship('Statistics', cascade='all', uselist=False)
1652
1661
1653 followers = relationship(
1662 followers = relationship(
1654 'UserFollowing',
1663 'UserFollowing',
1655 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1664 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1656 cascade='all')
1665 cascade='all')
1657 extra_fields = relationship(
1666 extra_fields = relationship(
1658 'RepositoryField', cascade="all, delete, delete-orphan")
1667 'RepositoryField', cascade="all, delete, delete-orphan")
1659 logs = relationship('UserLog')
1668 logs = relationship('UserLog')
1660 comments = relationship(
1669 comments = relationship(
1661 'ChangesetComment', cascade="all, delete, delete-orphan")
1670 'ChangesetComment', cascade="all, delete, delete-orphan")
1662 pull_requests_source = relationship(
1671 pull_requests_source = relationship(
1663 'PullRequest',
1672 'PullRequest',
1664 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1673 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1665 cascade="all, delete, delete-orphan")
1674 cascade="all, delete, delete-orphan")
1666 pull_requests_target = relationship(
1675 pull_requests_target = relationship(
1667 'PullRequest',
1676 'PullRequest',
1668 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1677 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1669 cascade="all, delete, delete-orphan")
1678 cascade="all, delete, delete-orphan")
1670 ui = relationship('RepoRhodeCodeUi', cascade="all")
1679 ui = relationship('RepoRhodeCodeUi', cascade="all")
1671 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1680 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1672 integrations = relationship('Integration',
1681 integrations = relationship('Integration',
1673 cascade="all, delete, delete-orphan")
1682 cascade="all, delete, delete-orphan")
1674
1683
1675 scoped_tokens = relationship('UserApiKeys', cascade="all")
1684 scoped_tokens = relationship('UserApiKeys', cascade="all")
1676
1685
1677 def __unicode__(self):
1686 def __unicode__(self):
1678 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1687 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1679 safe_unicode(self.repo_name))
1688 safe_unicode(self.repo_name))
1680
1689
1681 @hybrid_property
1690 @hybrid_property
1682 def description_safe(self):
1691 def description_safe(self):
1683 from rhodecode.lib import helpers as h
1692 from rhodecode.lib import helpers as h
1684 return h.escape(self.description)
1693 return h.escape(self.description)
1685
1694
1686 @hybrid_property
1695 @hybrid_property
1687 def landing_rev(self):
1696 def landing_rev(self):
1688 # always should return [rev_type, rev]
1697 # always should return [rev_type, rev]
1689 if self._landing_revision:
1698 if self._landing_revision:
1690 _rev_info = self._landing_revision.split(':')
1699 _rev_info = self._landing_revision.split(':')
1691 if len(_rev_info) < 2:
1700 if len(_rev_info) < 2:
1692 _rev_info.insert(0, 'rev')
1701 _rev_info.insert(0, 'rev')
1693 return [_rev_info[0], _rev_info[1]]
1702 return [_rev_info[0], _rev_info[1]]
1694 return [None, None]
1703 return [None, None]
1695
1704
1696 @landing_rev.setter
1705 @landing_rev.setter
1697 def landing_rev(self, val):
1706 def landing_rev(self, val):
1698 if ':' not in val:
1707 if ':' not in val:
1699 raise ValueError('value must be delimited with `:` and consist '
1708 raise ValueError('value must be delimited with `:` and consist '
1700 'of <rev_type>:<rev>, got %s instead' % val)
1709 'of <rev_type>:<rev>, got %s instead' % val)
1701 self._landing_revision = val
1710 self._landing_revision = val
1702
1711
1703 @hybrid_property
1712 @hybrid_property
1704 def locked(self):
1713 def locked(self):
1705 if self._locked:
1714 if self._locked:
1706 user_id, timelocked, reason = self._locked.split(':')
1715 user_id, timelocked, reason = self._locked.split(':')
1707 lock_values = int(user_id), timelocked, reason
1716 lock_values = int(user_id), timelocked, reason
1708 else:
1717 else:
1709 lock_values = [None, None, None]
1718 lock_values = [None, None, None]
1710 return lock_values
1719 return lock_values
1711
1720
1712 @locked.setter
1721 @locked.setter
1713 def locked(self, val):
1722 def locked(self, val):
1714 if val and isinstance(val, (list, tuple)):
1723 if val and isinstance(val, (list, tuple)):
1715 self._locked = ':'.join(map(str, val))
1724 self._locked = ':'.join(map(str, val))
1716 else:
1725 else:
1717 self._locked = None
1726 self._locked = None
1718
1727
1719 @hybrid_property
1728 @hybrid_property
1720 def changeset_cache(self):
1729 def changeset_cache(self):
1721 from rhodecode.lib.vcs.backends.base import EmptyCommit
1730 from rhodecode.lib.vcs.backends.base import EmptyCommit
1722 dummy = EmptyCommit().__json__()
1731 dummy = EmptyCommit().__json__()
1723 if not self._changeset_cache:
1732 if not self._changeset_cache:
1724 return dummy
1733 return dummy
1725 try:
1734 try:
1726 return json.loads(self._changeset_cache)
1735 return json.loads(self._changeset_cache)
1727 except TypeError:
1736 except TypeError:
1728 return dummy
1737 return dummy
1729 except Exception:
1738 except Exception:
1730 log.error(traceback.format_exc())
1739 log.error(traceback.format_exc())
1731 return dummy
1740 return dummy
1732
1741
1733 @changeset_cache.setter
1742 @changeset_cache.setter
1734 def changeset_cache(self, val):
1743 def changeset_cache(self, val):
1735 try:
1744 try:
1736 self._changeset_cache = json.dumps(val)
1745 self._changeset_cache = json.dumps(val)
1737 except Exception:
1746 except Exception:
1738 log.error(traceback.format_exc())
1747 log.error(traceback.format_exc())
1739
1748
1740 @hybrid_property
1749 @hybrid_property
1741 def repo_name(self):
1750 def repo_name(self):
1742 return self._repo_name
1751 return self._repo_name
1743
1752
1744 @repo_name.setter
1753 @repo_name.setter
1745 def repo_name(self, value):
1754 def repo_name(self, value):
1746 self._repo_name = value
1755 self._repo_name = value
1747 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1756 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1748
1757
1749 @classmethod
1758 @classmethod
1750 def normalize_repo_name(cls, repo_name):
1759 def normalize_repo_name(cls, repo_name):
1751 """
1760 """
1752 Normalizes os specific repo_name to the format internally stored inside
1761 Normalizes os specific repo_name to the format internally stored inside
1753 database using URL_SEP
1762 database using URL_SEP
1754
1763
1755 :param cls:
1764 :param cls:
1756 :param repo_name:
1765 :param repo_name:
1757 """
1766 """
1758 return cls.NAME_SEP.join(repo_name.split(os.sep))
1767 return cls.NAME_SEP.join(repo_name.split(os.sep))
1759
1768
1760 @classmethod
1769 @classmethod
1761 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1770 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1762 session = Session()
1771 session = Session()
1763 q = session.query(cls).filter(cls.repo_name == repo_name)
1772 q = session.query(cls).filter(cls.repo_name == repo_name)
1764
1773
1765 if cache:
1774 if cache:
1766 if identity_cache:
1775 if identity_cache:
1767 val = cls.identity_cache(session, 'repo_name', repo_name)
1776 val = cls.identity_cache(session, 'repo_name', repo_name)
1768 if val:
1777 if val:
1769 return val
1778 return val
1770 else:
1779 else:
1771 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1780 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1772 q = q.options(
1781 q = q.options(
1773 FromCache("sql_cache_short", cache_key))
1782 FromCache("sql_cache_short", cache_key))
1774
1783
1775 return q.scalar()
1784 return q.scalar()
1776
1785
1777 @classmethod
1786 @classmethod
1778 def get_by_id_or_repo_name(cls, repoid):
1787 def get_by_id_or_repo_name(cls, repoid):
1779 if isinstance(repoid, (int, long)):
1788 if isinstance(repoid, (int, long)):
1780 try:
1789 try:
1781 repo = cls.get(repoid)
1790 repo = cls.get(repoid)
1782 except ValueError:
1791 except ValueError:
1783 repo = None
1792 repo = None
1784 else:
1793 else:
1785 repo = cls.get_by_repo_name(repoid)
1794 repo = cls.get_by_repo_name(repoid)
1786 return repo
1795 return repo
1787
1796
1788 @classmethod
1797 @classmethod
1789 def get_by_full_path(cls, repo_full_path):
1798 def get_by_full_path(cls, repo_full_path):
1790 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1799 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1791 repo_name = cls.normalize_repo_name(repo_name)
1800 repo_name = cls.normalize_repo_name(repo_name)
1792 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1801 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1793
1802
1794 @classmethod
1803 @classmethod
1795 def get_repo_forks(cls, repo_id):
1804 def get_repo_forks(cls, repo_id):
1796 return cls.query().filter(Repository.fork_id == repo_id)
1805 return cls.query().filter(Repository.fork_id == repo_id)
1797
1806
1798 @classmethod
1807 @classmethod
1799 def base_path(cls):
1808 def base_path(cls):
1800 """
1809 """
1801 Returns base path when all repos are stored
1810 Returns base path when all repos are stored
1802
1811
1803 :param cls:
1812 :param cls:
1804 """
1813 """
1805 q = Session().query(RhodeCodeUi)\
1814 q = Session().query(RhodeCodeUi)\
1806 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1815 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1807 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1816 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1808 return q.one().ui_value
1817 return q.one().ui_value
1809
1818
1810 @classmethod
1819 @classmethod
1811 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1820 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1812 case_insensitive=True, archived=False):
1821 case_insensitive=True, archived=False):
1813 q = Repository.query()
1822 q = Repository.query()
1814
1823
1815 if not archived:
1824 if not archived:
1816 q = q.filter(Repository.archived.isnot(true()))
1825 q = q.filter(Repository.archived.isnot(true()))
1817
1826
1818 if not isinstance(user_id, Optional):
1827 if not isinstance(user_id, Optional):
1819 q = q.filter(Repository.user_id == user_id)
1828 q = q.filter(Repository.user_id == user_id)
1820
1829
1821 if not isinstance(group_id, Optional):
1830 if not isinstance(group_id, Optional):
1822 q = q.filter(Repository.group_id == group_id)
1831 q = q.filter(Repository.group_id == group_id)
1823
1832
1824 if case_insensitive:
1833 if case_insensitive:
1825 q = q.order_by(func.lower(Repository.repo_name))
1834 q = q.order_by(func.lower(Repository.repo_name))
1826 else:
1835 else:
1827 q = q.order_by(Repository.repo_name)
1836 q = q.order_by(Repository.repo_name)
1828
1837
1829 return q.all()
1838 return q.all()
1830
1839
1831 @property
1840 @property
1832 def forks(self):
1841 def forks(self):
1833 """
1842 """
1834 Return forks of this repo
1843 Return forks of this repo
1835 """
1844 """
1836 return Repository.get_repo_forks(self.repo_id)
1845 return Repository.get_repo_forks(self.repo_id)
1837
1846
1838 @property
1847 @property
1839 def parent(self):
1848 def parent(self):
1840 """
1849 """
1841 Returns fork parent
1850 Returns fork parent
1842 """
1851 """
1843 return self.fork
1852 return self.fork
1844
1853
1845 @property
1854 @property
1846 def just_name(self):
1855 def just_name(self):
1847 return self.repo_name.split(self.NAME_SEP)[-1]
1856 return self.repo_name.split(self.NAME_SEP)[-1]
1848
1857
1849 @property
1858 @property
1850 def groups_with_parents(self):
1859 def groups_with_parents(self):
1851 groups = []
1860 groups = []
1852 if self.group is None:
1861 if self.group is None:
1853 return groups
1862 return groups
1854
1863
1855 cur_gr = self.group
1864 cur_gr = self.group
1856 groups.insert(0, cur_gr)
1865 groups.insert(0, cur_gr)
1857 while 1:
1866 while 1:
1858 gr = getattr(cur_gr, 'parent_group', None)
1867 gr = getattr(cur_gr, 'parent_group', None)
1859 cur_gr = cur_gr.parent_group
1868 cur_gr = cur_gr.parent_group
1860 if gr is None:
1869 if gr is None:
1861 break
1870 break
1862 groups.insert(0, gr)
1871 groups.insert(0, gr)
1863
1872
1864 return groups
1873 return groups
1865
1874
1866 @property
1875 @property
1867 def groups_and_repo(self):
1876 def groups_and_repo(self):
1868 return self.groups_with_parents, self
1877 return self.groups_with_parents, self
1869
1878
1870 @LazyProperty
1879 @LazyProperty
1871 def repo_path(self):
1880 def repo_path(self):
1872 """
1881 """
1873 Returns base full path for that repository means where it actually
1882 Returns base full path for that repository means where it actually
1874 exists on a filesystem
1883 exists on a filesystem
1875 """
1884 """
1876 q = Session().query(RhodeCodeUi).filter(
1885 q = Session().query(RhodeCodeUi).filter(
1877 RhodeCodeUi.ui_key == self.NAME_SEP)
1886 RhodeCodeUi.ui_key == self.NAME_SEP)
1878 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1887 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1879 return q.one().ui_value
1888 return q.one().ui_value
1880
1889
1881 @property
1890 @property
1882 def repo_full_path(self):
1891 def repo_full_path(self):
1883 p = [self.repo_path]
1892 p = [self.repo_path]
1884 # we need to split the name by / since this is how we store the
1893 # we need to split the name by / since this is how we store the
1885 # names in the database, but that eventually needs to be converted
1894 # names in the database, but that eventually needs to be converted
1886 # into a valid system path
1895 # into a valid system path
1887 p += self.repo_name.split(self.NAME_SEP)
1896 p += self.repo_name.split(self.NAME_SEP)
1888 return os.path.join(*map(safe_unicode, p))
1897 return os.path.join(*map(safe_unicode, p))
1889
1898
1890 @property
1899 @property
1891 def cache_keys(self):
1900 def cache_keys(self):
1892 """
1901 """
1893 Returns associated cache keys for that repo
1902 Returns associated cache keys for that repo
1894 """
1903 """
1895 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1904 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1896 repo_id=self.repo_id)
1905 repo_id=self.repo_id)
1897 return CacheKey.query()\
1906 return CacheKey.query()\
1898 .filter(CacheKey.cache_args == invalidation_namespace)\
1907 .filter(CacheKey.cache_args == invalidation_namespace)\
1899 .order_by(CacheKey.cache_key)\
1908 .order_by(CacheKey.cache_key)\
1900 .all()
1909 .all()
1901
1910
1902 @property
1911 @property
1903 def cached_diffs_relative_dir(self):
1912 def cached_diffs_relative_dir(self):
1904 """
1913 """
1905 Return a relative to the repository store path of cached diffs
1914 Return a relative to the repository store path of cached diffs
1906 used for safe display for users, who shouldn't know the absolute store
1915 used for safe display for users, who shouldn't know the absolute store
1907 path
1916 path
1908 """
1917 """
1909 return os.path.join(
1918 return os.path.join(
1910 os.path.dirname(self.repo_name),
1919 os.path.dirname(self.repo_name),
1911 self.cached_diffs_dir.split(os.path.sep)[-1])
1920 self.cached_diffs_dir.split(os.path.sep)[-1])
1912
1921
1913 @property
1922 @property
1914 def cached_diffs_dir(self):
1923 def cached_diffs_dir(self):
1915 path = self.repo_full_path
1924 path = self.repo_full_path
1916 return os.path.join(
1925 return os.path.join(
1917 os.path.dirname(path),
1926 os.path.dirname(path),
1918 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1927 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1919
1928
1920 def cached_diffs(self):
1929 def cached_diffs(self):
1921 diff_cache_dir = self.cached_diffs_dir
1930 diff_cache_dir = self.cached_diffs_dir
1922 if os.path.isdir(diff_cache_dir):
1931 if os.path.isdir(diff_cache_dir):
1923 return os.listdir(diff_cache_dir)
1932 return os.listdir(diff_cache_dir)
1924 return []
1933 return []
1925
1934
1926 def shadow_repos(self):
1935 def shadow_repos(self):
1927 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1936 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1928 return [
1937 return [
1929 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1938 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1930 if x.startswith(shadow_repos_pattern)]
1939 if x.startswith(shadow_repos_pattern)]
1931
1940
1932 def get_new_name(self, repo_name):
1941 def get_new_name(self, repo_name):
1933 """
1942 """
1934 returns new full repository name based on assigned group and new new
1943 returns new full repository name based on assigned group and new new
1935
1944
1936 :param group_name:
1945 :param group_name:
1937 """
1946 """
1938 path_prefix = self.group.full_path_splitted if self.group else []
1947 path_prefix = self.group.full_path_splitted if self.group else []
1939 return self.NAME_SEP.join(path_prefix + [repo_name])
1948 return self.NAME_SEP.join(path_prefix + [repo_name])
1940
1949
1941 @property
1950 @property
1942 def _config(self):
1951 def _config(self):
1943 """
1952 """
1944 Returns db based config object.
1953 Returns db based config object.
1945 """
1954 """
1946 from rhodecode.lib.utils import make_db_config
1955 from rhodecode.lib.utils import make_db_config
1947 return make_db_config(clear_session=False, repo=self)
1956 return make_db_config(clear_session=False, repo=self)
1948
1957
1949 def permissions(self, with_admins=True, with_owner=True,
1958 def permissions(self, with_admins=True, with_owner=True,
1950 expand_from_user_groups=False):
1959 expand_from_user_groups=False):
1951 """
1960 """
1952 Permissions for repositories
1961 Permissions for repositories
1953 """
1962 """
1954 _admin_perm = 'repository.admin'
1963 _admin_perm = 'repository.admin'
1955
1964
1956 owner_row = []
1965 owner_row = []
1957 if with_owner:
1966 if with_owner:
1958 usr = AttributeDict(self.user.get_dict())
1967 usr = AttributeDict(self.user.get_dict())
1959 usr.owner_row = True
1968 usr.owner_row = True
1960 usr.permission = _admin_perm
1969 usr.permission = _admin_perm
1961 usr.permission_id = None
1970 usr.permission_id = None
1962 owner_row.append(usr)
1971 owner_row.append(usr)
1963
1972
1964 super_admin_ids = []
1973 super_admin_ids = []
1965 super_admin_rows = []
1974 super_admin_rows = []
1966 if with_admins:
1975 if with_admins:
1967 for usr in User.get_all_super_admins():
1976 for usr in User.get_all_super_admins():
1968 super_admin_ids.append(usr.user_id)
1977 super_admin_ids.append(usr.user_id)
1969 # if this admin is also owner, don't double the record
1978 # if this admin is also owner, don't double the record
1970 if usr.user_id == owner_row[0].user_id:
1979 if usr.user_id == owner_row[0].user_id:
1971 owner_row[0].admin_row = True
1980 owner_row[0].admin_row = True
1972 else:
1981 else:
1973 usr = AttributeDict(usr.get_dict())
1982 usr = AttributeDict(usr.get_dict())
1974 usr.admin_row = True
1983 usr.admin_row = True
1975 usr.permission = _admin_perm
1984 usr.permission = _admin_perm
1976 usr.permission_id = None
1985 usr.permission_id = None
1977 super_admin_rows.append(usr)
1986 super_admin_rows.append(usr)
1978
1987
1979 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1988 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1980 q = q.options(joinedload(UserRepoToPerm.repository),
1989 q = q.options(joinedload(UserRepoToPerm.repository),
1981 joinedload(UserRepoToPerm.user),
1990 joinedload(UserRepoToPerm.user),
1982 joinedload(UserRepoToPerm.permission),)
1991 joinedload(UserRepoToPerm.permission),)
1983
1992
1984 # get owners and admins and permissions. We do a trick of re-writing
1993 # get owners and admins and permissions. We do a trick of re-writing
1985 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1994 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1986 # has a global reference and changing one object propagates to all
1995 # has a global reference and changing one object propagates to all
1987 # others. This means if admin is also an owner admin_row that change
1996 # others. This means if admin is also an owner admin_row that change
1988 # would propagate to both objects
1997 # would propagate to both objects
1989 perm_rows = []
1998 perm_rows = []
1990 for _usr in q.all():
1999 for _usr in q.all():
1991 usr = AttributeDict(_usr.user.get_dict())
2000 usr = AttributeDict(_usr.user.get_dict())
1992 # if this user is also owner/admin, mark as duplicate record
2001 # if this user is also owner/admin, mark as duplicate record
1993 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2002 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1994 usr.duplicate_perm = True
2003 usr.duplicate_perm = True
1995 # also check if this permission is maybe used by branch_permissions
2004 # also check if this permission is maybe used by branch_permissions
1996 if _usr.branch_perm_entry:
2005 if _usr.branch_perm_entry:
1997 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2006 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
1998
2007
1999 usr.permission = _usr.permission.permission_name
2008 usr.permission = _usr.permission.permission_name
2000 usr.permission_id = _usr.repo_to_perm_id
2009 usr.permission_id = _usr.repo_to_perm_id
2001 perm_rows.append(usr)
2010 perm_rows.append(usr)
2002
2011
2003 # filter the perm rows by 'default' first and then sort them by
2012 # filter the perm rows by 'default' first and then sort them by
2004 # admin,write,read,none permissions sorted again alphabetically in
2013 # admin,write,read,none permissions sorted again alphabetically in
2005 # each group
2014 # each group
2006 perm_rows = sorted(perm_rows, key=display_user_sort)
2015 perm_rows = sorted(perm_rows, key=display_user_sort)
2007
2016
2008 user_groups_rows = []
2017 user_groups_rows = []
2009 if expand_from_user_groups:
2018 if expand_from_user_groups:
2010 for ug in self.permission_user_groups(with_members=True):
2019 for ug in self.permission_user_groups(with_members=True):
2011 for user_data in ug.members:
2020 for user_data in ug.members:
2012 user_groups_rows.append(user_data)
2021 user_groups_rows.append(user_data)
2013
2022
2014 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2023 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2015
2024
2016 def permission_user_groups(self, with_members=True):
2025 def permission_user_groups(self, with_members=True):
2017 q = UserGroupRepoToPerm.query()\
2026 q = UserGroupRepoToPerm.query()\
2018 .filter(UserGroupRepoToPerm.repository == self)
2027 .filter(UserGroupRepoToPerm.repository == self)
2019 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2028 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2020 joinedload(UserGroupRepoToPerm.users_group),
2029 joinedload(UserGroupRepoToPerm.users_group),
2021 joinedload(UserGroupRepoToPerm.permission),)
2030 joinedload(UserGroupRepoToPerm.permission),)
2022
2031
2023 perm_rows = []
2032 perm_rows = []
2024 for _user_group in q.all():
2033 for _user_group in q.all():
2025 entry = AttributeDict(_user_group.users_group.get_dict())
2034 entry = AttributeDict(_user_group.users_group.get_dict())
2026 entry.permission = _user_group.permission.permission_name
2035 entry.permission = _user_group.permission.permission_name
2027 if with_members:
2036 if with_members:
2028 entry.members = [x.user.get_dict()
2037 entry.members = [x.user.get_dict()
2029 for x in _user_group.users_group.members]
2038 for x in _user_group.users_group.members]
2030 perm_rows.append(entry)
2039 perm_rows.append(entry)
2031
2040
2032 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2041 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2033 return perm_rows
2042 return perm_rows
2034
2043
2035 def get_api_data(self, include_secrets=False):
2044 def get_api_data(self, include_secrets=False):
2036 """
2045 """
2037 Common function for generating repo api data
2046 Common function for generating repo api data
2038
2047
2039 :param include_secrets: See :meth:`User.get_api_data`.
2048 :param include_secrets: See :meth:`User.get_api_data`.
2040
2049
2041 """
2050 """
2042 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2051 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2043 # move this methods on models level.
2052 # move this methods on models level.
2044 from rhodecode.model.settings import SettingsModel
2053 from rhodecode.model.settings import SettingsModel
2045 from rhodecode.model.repo import RepoModel
2054 from rhodecode.model.repo import RepoModel
2046
2055
2047 repo = self
2056 repo = self
2048 _user_id, _time, _reason = self.locked
2057 _user_id, _time, _reason = self.locked
2049
2058
2050 data = {
2059 data = {
2051 'repo_id': repo.repo_id,
2060 'repo_id': repo.repo_id,
2052 'repo_name': repo.repo_name,
2061 'repo_name': repo.repo_name,
2053 'repo_type': repo.repo_type,
2062 'repo_type': repo.repo_type,
2054 'clone_uri': repo.clone_uri or '',
2063 'clone_uri': repo.clone_uri or '',
2055 'push_uri': repo.push_uri or '',
2064 'push_uri': repo.push_uri or '',
2056 'url': RepoModel().get_url(self),
2065 'url': RepoModel().get_url(self),
2057 'private': repo.private,
2066 'private': repo.private,
2058 'created_on': repo.created_on,
2067 'created_on': repo.created_on,
2059 'description': repo.description_safe,
2068 'description': repo.description_safe,
2060 'landing_rev': repo.landing_rev,
2069 'landing_rev': repo.landing_rev,
2061 'owner': repo.user.username,
2070 'owner': repo.user.username,
2062 'fork_of': repo.fork.repo_name if repo.fork else None,
2071 'fork_of': repo.fork.repo_name if repo.fork else None,
2063 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2072 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2064 'enable_statistics': repo.enable_statistics,
2073 'enable_statistics': repo.enable_statistics,
2065 'enable_locking': repo.enable_locking,
2074 'enable_locking': repo.enable_locking,
2066 'enable_downloads': repo.enable_downloads,
2075 'enable_downloads': repo.enable_downloads,
2067 'last_changeset': repo.changeset_cache,
2076 'last_changeset': repo.changeset_cache,
2068 'locked_by': User.get(_user_id).get_api_data(
2077 'locked_by': User.get(_user_id).get_api_data(
2069 include_secrets=include_secrets) if _user_id else None,
2078 include_secrets=include_secrets) if _user_id else None,
2070 'locked_date': time_to_datetime(_time) if _time else None,
2079 'locked_date': time_to_datetime(_time) if _time else None,
2071 'lock_reason': _reason if _reason else None,
2080 'lock_reason': _reason if _reason else None,
2072 }
2081 }
2073
2082
2074 # TODO: mikhail: should be per-repo settings here
2083 # TODO: mikhail: should be per-repo settings here
2075 rc_config = SettingsModel().get_all_settings()
2084 rc_config = SettingsModel().get_all_settings()
2076 repository_fields = str2bool(
2085 repository_fields = str2bool(
2077 rc_config.get('rhodecode_repository_fields'))
2086 rc_config.get('rhodecode_repository_fields'))
2078 if repository_fields:
2087 if repository_fields:
2079 for f in self.extra_fields:
2088 for f in self.extra_fields:
2080 data[f.field_key_prefixed] = f.field_value
2089 data[f.field_key_prefixed] = f.field_value
2081
2090
2082 return data
2091 return data
2083
2092
2084 @classmethod
2093 @classmethod
2085 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2094 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2086 if not lock_time:
2095 if not lock_time:
2087 lock_time = time.time()
2096 lock_time = time.time()
2088 if not lock_reason:
2097 if not lock_reason:
2089 lock_reason = cls.LOCK_AUTOMATIC
2098 lock_reason = cls.LOCK_AUTOMATIC
2090 repo.locked = [user_id, lock_time, lock_reason]
2099 repo.locked = [user_id, lock_time, lock_reason]
2091 Session().add(repo)
2100 Session().add(repo)
2092 Session().commit()
2101 Session().commit()
2093
2102
2094 @classmethod
2103 @classmethod
2095 def unlock(cls, repo):
2104 def unlock(cls, repo):
2096 repo.locked = None
2105 repo.locked = None
2097 Session().add(repo)
2106 Session().add(repo)
2098 Session().commit()
2107 Session().commit()
2099
2108
2100 @classmethod
2109 @classmethod
2101 def getlock(cls, repo):
2110 def getlock(cls, repo):
2102 return repo.locked
2111 return repo.locked
2103
2112
2104 def is_user_lock(self, user_id):
2113 def is_user_lock(self, user_id):
2105 if self.lock[0]:
2114 if self.lock[0]:
2106 lock_user_id = safe_int(self.lock[0])
2115 lock_user_id = safe_int(self.lock[0])
2107 user_id = safe_int(user_id)
2116 user_id = safe_int(user_id)
2108 # both are ints, and they are equal
2117 # both are ints, and they are equal
2109 return all([lock_user_id, user_id]) and lock_user_id == user_id
2118 return all([lock_user_id, user_id]) and lock_user_id == user_id
2110
2119
2111 return False
2120 return False
2112
2121
2113 def get_locking_state(self, action, user_id, only_when_enabled=True):
2122 def get_locking_state(self, action, user_id, only_when_enabled=True):
2114 """
2123 """
2115 Checks locking on this repository, if locking is enabled and lock is
2124 Checks locking on this repository, if locking is enabled and lock is
2116 present returns a tuple of make_lock, locked, locked_by.
2125 present returns a tuple of make_lock, locked, locked_by.
2117 make_lock can have 3 states None (do nothing) True, make lock
2126 make_lock can have 3 states None (do nothing) True, make lock
2118 False release lock, This value is later propagated to hooks, which
2127 False release lock, This value is later propagated to hooks, which
2119 do the locking. Think about this as signals passed to hooks what to do.
2128 do the locking. Think about this as signals passed to hooks what to do.
2120
2129
2121 """
2130 """
2122 # TODO: johbo: This is part of the business logic and should be moved
2131 # TODO: johbo: This is part of the business logic and should be moved
2123 # into the RepositoryModel.
2132 # into the RepositoryModel.
2124
2133
2125 if action not in ('push', 'pull'):
2134 if action not in ('push', 'pull'):
2126 raise ValueError("Invalid action value: %s" % repr(action))
2135 raise ValueError("Invalid action value: %s" % repr(action))
2127
2136
2128 # defines if locked error should be thrown to user
2137 # defines if locked error should be thrown to user
2129 currently_locked = False
2138 currently_locked = False
2130 # defines if new lock should be made, tri-state
2139 # defines if new lock should be made, tri-state
2131 make_lock = None
2140 make_lock = None
2132 repo = self
2141 repo = self
2133 user = User.get(user_id)
2142 user = User.get(user_id)
2134
2143
2135 lock_info = repo.locked
2144 lock_info = repo.locked
2136
2145
2137 if repo and (repo.enable_locking or not only_when_enabled):
2146 if repo and (repo.enable_locking or not only_when_enabled):
2138 if action == 'push':
2147 if action == 'push':
2139 # check if it's already locked !, if it is compare users
2148 # check if it's already locked !, if it is compare users
2140 locked_by_user_id = lock_info[0]
2149 locked_by_user_id = lock_info[0]
2141 if user.user_id == locked_by_user_id:
2150 if user.user_id == locked_by_user_id:
2142 log.debug(
2151 log.debug(
2143 'Got `push` action from user %s, now unlocking', user)
2152 'Got `push` action from user %s, now unlocking', user)
2144 # unlock if we have push from user who locked
2153 # unlock if we have push from user who locked
2145 make_lock = False
2154 make_lock = False
2146 else:
2155 else:
2147 # we're not the same user who locked, ban with
2156 # we're not the same user who locked, ban with
2148 # code defined in settings (default is 423 HTTP Locked) !
2157 # code defined in settings (default is 423 HTTP Locked) !
2149 log.debug('Repo %s is currently locked by %s', repo, user)
2158 log.debug('Repo %s is currently locked by %s', repo, user)
2150 currently_locked = True
2159 currently_locked = True
2151 elif action == 'pull':
2160 elif action == 'pull':
2152 # [0] user [1] date
2161 # [0] user [1] date
2153 if lock_info[0] and lock_info[1]:
2162 if lock_info[0] and lock_info[1]:
2154 log.debug('Repo %s is currently locked by %s', repo, user)
2163 log.debug('Repo %s is currently locked by %s', repo, user)
2155 currently_locked = True
2164 currently_locked = True
2156 else:
2165 else:
2157 log.debug('Setting lock on repo %s by %s', repo, user)
2166 log.debug('Setting lock on repo %s by %s', repo, user)
2158 make_lock = True
2167 make_lock = True
2159
2168
2160 else:
2169 else:
2161 log.debug('Repository %s do not have locking enabled', repo)
2170 log.debug('Repository %s do not have locking enabled', repo)
2162
2171
2163 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2172 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2164 make_lock, currently_locked, lock_info)
2173 make_lock, currently_locked, lock_info)
2165
2174
2166 from rhodecode.lib.auth import HasRepoPermissionAny
2175 from rhodecode.lib.auth import HasRepoPermissionAny
2167 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2176 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2168 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2177 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2169 # if we don't have at least write permission we cannot make a lock
2178 # if we don't have at least write permission we cannot make a lock
2170 log.debug('lock state reset back to FALSE due to lack '
2179 log.debug('lock state reset back to FALSE due to lack '
2171 'of at least read permission')
2180 'of at least read permission')
2172 make_lock = False
2181 make_lock = False
2173
2182
2174 return make_lock, currently_locked, lock_info
2183 return make_lock, currently_locked, lock_info
2175
2184
2176 @property
2185 @property
2177 def last_db_change(self):
2186 def last_db_change(self):
2178 return self.updated_on
2187 return self.updated_on
2179
2188
2180 @property
2189 @property
2181 def clone_uri_hidden(self):
2190 def clone_uri_hidden(self):
2182 clone_uri = self.clone_uri
2191 clone_uri = self.clone_uri
2183 if clone_uri:
2192 if clone_uri:
2184 import urlobject
2193 import urlobject
2185 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2194 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2186 if url_obj.password:
2195 if url_obj.password:
2187 clone_uri = url_obj.with_password('*****')
2196 clone_uri = url_obj.with_password('*****')
2188 return clone_uri
2197 return clone_uri
2189
2198
2190 @property
2199 @property
2191 def push_uri_hidden(self):
2200 def push_uri_hidden(self):
2192 push_uri = self.push_uri
2201 push_uri = self.push_uri
2193 if push_uri:
2202 if push_uri:
2194 import urlobject
2203 import urlobject
2195 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2204 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2196 if url_obj.password:
2205 if url_obj.password:
2197 push_uri = url_obj.with_password('*****')
2206 push_uri = url_obj.with_password('*****')
2198 return push_uri
2207 return push_uri
2199
2208
2200 def clone_url(self, **override):
2209 def clone_url(self, **override):
2201 from rhodecode.model.settings import SettingsModel
2210 from rhodecode.model.settings import SettingsModel
2202
2211
2203 uri_tmpl = None
2212 uri_tmpl = None
2204 if 'with_id' in override:
2213 if 'with_id' in override:
2205 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2214 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2206 del override['with_id']
2215 del override['with_id']
2207
2216
2208 if 'uri_tmpl' in override:
2217 if 'uri_tmpl' in override:
2209 uri_tmpl = override['uri_tmpl']
2218 uri_tmpl = override['uri_tmpl']
2210 del override['uri_tmpl']
2219 del override['uri_tmpl']
2211
2220
2212 ssh = False
2221 ssh = False
2213 if 'ssh' in override:
2222 if 'ssh' in override:
2214 ssh = True
2223 ssh = True
2215 del override['ssh']
2224 del override['ssh']
2216
2225
2217 # we didn't override our tmpl from **overrides
2226 # we didn't override our tmpl from **overrides
2218 if not uri_tmpl:
2227 if not uri_tmpl:
2219 rc_config = SettingsModel().get_all_settings(cache=True)
2228 rc_config = SettingsModel().get_all_settings(cache=True)
2220 if ssh:
2229 if ssh:
2221 uri_tmpl = rc_config.get(
2230 uri_tmpl = rc_config.get(
2222 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2231 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2223 else:
2232 else:
2224 uri_tmpl = rc_config.get(
2233 uri_tmpl = rc_config.get(
2225 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2234 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2226
2235
2227 request = get_current_request()
2236 request = get_current_request()
2228 return get_clone_url(request=request,
2237 return get_clone_url(request=request,
2229 uri_tmpl=uri_tmpl,
2238 uri_tmpl=uri_tmpl,
2230 repo_name=self.repo_name,
2239 repo_name=self.repo_name,
2231 repo_id=self.repo_id, **override)
2240 repo_id=self.repo_id, **override)
2232
2241
2233 def set_state(self, state):
2242 def set_state(self, state):
2234 self.repo_state = state
2243 self.repo_state = state
2235 Session().add(self)
2244 Session().add(self)
2236 #==========================================================================
2245 #==========================================================================
2237 # SCM PROPERTIES
2246 # SCM PROPERTIES
2238 #==========================================================================
2247 #==========================================================================
2239
2248
2240 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2249 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2241 return get_commit_safe(
2250 return get_commit_safe(
2242 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2251 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2243
2252
2244 def get_changeset(self, rev=None, pre_load=None):
2253 def get_changeset(self, rev=None, pre_load=None):
2245 warnings.warn("Use get_commit", DeprecationWarning)
2254 warnings.warn("Use get_commit", DeprecationWarning)
2246 commit_id = None
2255 commit_id = None
2247 commit_idx = None
2256 commit_idx = None
2248 if isinstance(rev, compat.string_types):
2257 if isinstance(rev, compat.string_types):
2249 commit_id = rev
2258 commit_id = rev
2250 else:
2259 else:
2251 commit_idx = rev
2260 commit_idx = rev
2252 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2261 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2253 pre_load=pre_load)
2262 pre_load=pre_load)
2254
2263
2255 def get_landing_commit(self):
2264 def get_landing_commit(self):
2256 """
2265 """
2257 Returns landing commit, or if that doesn't exist returns the tip
2266 Returns landing commit, or if that doesn't exist returns the tip
2258 """
2267 """
2259 _rev_type, _rev = self.landing_rev
2268 _rev_type, _rev = self.landing_rev
2260 commit = self.get_commit(_rev)
2269 commit = self.get_commit(_rev)
2261 if isinstance(commit, EmptyCommit):
2270 if isinstance(commit, EmptyCommit):
2262 return self.get_commit()
2271 return self.get_commit()
2263 return commit
2272 return commit
2264
2273
2265 def update_commit_cache(self, cs_cache=None, config=None):
2274 def update_commit_cache(self, cs_cache=None, config=None):
2266 """
2275 """
2267 Update cache of last changeset for repository, keys should be::
2276 Update cache of last changeset for repository, keys should be::
2268
2277
2269 short_id
2278 short_id
2270 raw_id
2279 raw_id
2271 revision
2280 revision
2272 parents
2281 parents
2273 message
2282 message
2274 date
2283 date
2275 author
2284 author
2276
2285
2277 :param cs_cache:
2286 :param cs_cache:
2278 """
2287 """
2279 from rhodecode.lib.vcs.backends.base import BaseChangeset
2288 from rhodecode.lib.vcs.backends.base import BaseChangeset
2280 if cs_cache is None:
2289 if cs_cache is None:
2281 # use no-cache version here
2290 # use no-cache version here
2282 scm_repo = self.scm_instance(cache=False, config=config)
2291 scm_repo = self.scm_instance(cache=False, config=config)
2283
2292
2284 empty = not scm_repo or scm_repo.is_empty()
2293 empty = not scm_repo or scm_repo.is_empty()
2285 if not empty:
2294 if not empty:
2286 cs_cache = scm_repo.get_commit(
2295 cs_cache = scm_repo.get_commit(
2287 pre_load=["author", "date", "message", "parents"])
2296 pre_load=["author", "date", "message", "parents"])
2288 else:
2297 else:
2289 cs_cache = EmptyCommit()
2298 cs_cache = EmptyCommit()
2290
2299
2291 if isinstance(cs_cache, BaseChangeset):
2300 if isinstance(cs_cache, BaseChangeset):
2292 cs_cache = cs_cache.__json__()
2301 cs_cache = cs_cache.__json__()
2293
2302
2294 def is_outdated(new_cs_cache):
2303 def is_outdated(new_cs_cache):
2295 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2304 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2296 new_cs_cache['revision'] != self.changeset_cache['revision']):
2305 new_cs_cache['revision'] != self.changeset_cache['revision']):
2297 return True
2306 return True
2298 return False
2307 return False
2299
2308
2300 # check if we have maybe already latest cached revision
2309 # check if we have maybe already latest cached revision
2301 if is_outdated(cs_cache) or not self.changeset_cache:
2310 if is_outdated(cs_cache) or not self.changeset_cache:
2302 _default = datetime.datetime.utcnow()
2311 _default = datetime.datetime.utcnow()
2303 last_change = cs_cache.get('date') or _default
2312 last_change = cs_cache.get('date') or _default
2304 if self.updated_on and self.updated_on > last_change:
2313 if self.updated_on and self.updated_on > last_change:
2305 # we check if last update is newer than the new value
2314 # we check if last update is newer than the new value
2306 # if yes, we use the current timestamp instead. Imagine you get
2315 # if yes, we use the current timestamp instead. Imagine you get
2307 # old commit pushed 1y ago, we'd set last update 1y to ago.
2316 # old commit pushed 1y ago, we'd set last update 1y to ago.
2308 last_change = _default
2317 last_change = _default
2309 log.debug('updated repo %s with new cs cache %s',
2318 log.debug('updated repo %s with new cs cache %s',
2310 self.repo_name, cs_cache)
2319 self.repo_name, cs_cache)
2311 self.updated_on = last_change
2320 self.updated_on = last_change
2312 self.changeset_cache = cs_cache
2321 self.changeset_cache = cs_cache
2313 Session().add(self)
2322 Session().add(self)
2314 Session().commit()
2323 Session().commit()
2315 else:
2324 else:
2316 log.debug('Skipping update_commit_cache for repo:`%s` '
2325 log.debug('Skipping update_commit_cache for repo:`%s` '
2317 'commit already with latest changes', self.repo_name)
2326 'commit already with latest changes', self.repo_name)
2318
2327
2319 @property
2328 @property
2320 def tip(self):
2329 def tip(self):
2321 return self.get_commit('tip')
2330 return self.get_commit('tip')
2322
2331
2323 @property
2332 @property
2324 def author(self):
2333 def author(self):
2325 return self.tip.author
2334 return self.tip.author
2326
2335
2327 @property
2336 @property
2328 def last_change(self):
2337 def last_change(self):
2329 return self.scm_instance().last_change
2338 return self.scm_instance().last_change
2330
2339
2331 def get_comments(self, revisions=None):
2340 def get_comments(self, revisions=None):
2332 """
2341 """
2333 Returns comments for this repository grouped by revisions
2342 Returns comments for this repository grouped by revisions
2334
2343
2335 :param revisions: filter query by revisions only
2344 :param revisions: filter query by revisions only
2336 """
2345 """
2337 cmts = ChangesetComment.query()\
2346 cmts = ChangesetComment.query()\
2338 .filter(ChangesetComment.repo == self)
2347 .filter(ChangesetComment.repo == self)
2339 if revisions:
2348 if revisions:
2340 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2349 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2341 grouped = collections.defaultdict(list)
2350 grouped = collections.defaultdict(list)
2342 for cmt in cmts.all():
2351 for cmt in cmts.all():
2343 grouped[cmt.revision].append(cmt)
2352 grouped[cmt.revision].append(cmt)
2344 return grouped
2353 return grouped
2345
2354
2346 def statuses(self, revisions=None):
2355 def statuses(self, revisions=None):
2347 """
2356 """
2348 Returns statuses for this repository
2357 Returns statuses for this repository
2349
2358
2350 :param revisions: list of revisions to get statuses for
2359 :param revisions: list of revisions to get statuses for
2351 """
2360 """
2352 statuses = ChangesetStatus.query()\
2361 statuses = ChangesetStatus.query()\
2353 .filter(ChangesetStatus.repo == self)\
2362 .filter(ChangesetStatus.repo == self)\
2354 .filter(ChangesetStatus.version == 0)
2363 .filter(ChangesetStatus.version == 0)
2355
2364
2356 if revisions:
2365 if revisions:
2357 # Try doing the filtering in chunks to avoid hitting limits
2366 # Try doing the filtering in chunks to avoid hitting limits
2358 size = 500
2367 size = 500
2359 status_results = []
2368 status_results = []
2360 for chunk in xrange(0, len(revisions), size):
2369 for chunk in xrange(0, len(revisions), size):
2361 status_results += statuses.filter(
2370 status_results += statuses.filter(
2362 ChangesetStatus.revision.in_(
2371 ChangesetStatus.revision.in_(
2363 revisions[chunk: chunk+size])
2372 revisions[chunk: chunk+size])
2364 ).all()
2373 ).all()
2365 else:
2374 else:
2366 status_results = statuses.all()
2375 status_results = statuses.all()
2367
2376
2368 grouped = {}
2377 grouped = {}
2369
2378
2370 # maybe we have open new pullrequest without a status?
2379 # maybe we have open new pullrequest without a status?
2371 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2380 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2372 status_lbl = ChangesetStatus.get_status_lbl(stat)
2381 status_lbl = ChangesetStatus.get_status_lbl(stat)
2373 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2382 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2374 for rev in pr.revisions:
2383 for rev in pr.revisions:
2375 pr_id = pr.pull_request_id
2384 pr_id = pr.pull_request_id
2376 pr_repo = pr.target_repo.repo_name
2385 pr_repo = pr.target_repo.repo_name
2377 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2386 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2378
2387
2379 for stat in status_results:
2388 for stat in status_results:
2380 pr_id = pr_repo = None
2389 pr_id = pr_repo = None
2381 if stat.pull_request:
2390 if stat.pull_request:
2382 pr_id = stat.pull_request.pull_request_id
2391 pr_id = stat.pull_request.pull_request_id
2383 pr_repo = stat.pull_request.target_repo.repo_name
2392 pr_repo = stat.pull_request.target_repo.repo_name
2384 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2393 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2385 pr_id, pr_repo]
2394 pr_id, pr_repo]
2386 return grouped
2395 return grouped
2387
2396
2388 # ==========================================================================
2397 # ==========================================================================
2389 # SCM CACHE INSTANCE
2398 # SCM CACHE INSTANCE
2390 # ==========================================================================
2399 # ==========================================================================
2391
2400
2392 def scm_instance(self, **kwargs):
2401 def scm_instance(self, **kwargs):
2393 import rhodecode
2402 import rhodecode
2394
2403
2395 # Passing a config will not hit the cache currently only used
2404 # Passing a config will not hit the cache currently only used
2396 # for repo2dbmapper
2405 # for repo2dbmapper
2397 config = kwargs.pop('config', None)
2406 config = kwargs.pop('config', None)
2398 cache = kwargs.pop('cache', None)
2407 cache = kwargs.pop('cache', None)
2399 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2408 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2400 # if cache is NOT defined use default global, else we have a full
2409 # if cache is NOT defined use default global, else we have a full
2401 # control over cache behaviour
2410 # control over cache behaviour
2402 if cache is None and full_cache and not config:
2411 if cache is None and full_cache and not config:
2403 return self._get_instance_cached()
2412 return self._get_instance_cached()
2404 return self._get_instance(cache=bool(cache), config=config)
2413 return self._get_instance(cache=bool(cache), config=config)
2405
2414
2406 def _get_instance_cached(self):
2415 def _get_instance_cached(self):
2407 from rhodecode.lib import rc_cache
2416 from rhodecode.lib import rc_cache
2408
2417
2409 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2418 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2410 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2419 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2411 repo_id=self.repo_id)
2420 repo_id=self.repo_id)
2412 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2421 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2413
2422
2414 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2423 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2415 def get_instance_cached(repo_id, context_id):
2424 def get_instance_cached(repo_id, context_id):
2416 return self._get_instance()
2425 return self._get_instance()
2417
2426
2418 # we must use thread scoped cache here,
2427 # we must use thread scoped cache here,
2419 # because each thread of gevent needs it's own not shared connection and cache
2428 # because each thread of gevent needs it's own not shared connection and cache
2420 # we also alter `args` so the cache key is individual for every green thread.
2429 # we also alter `args` so the cache key is individual for every green thread.
2421 inv_context_manager = rc_cache.InvalidationContext(
2430 inv_context_manager = rc_cache.InvalidationContext(
2422 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2431 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2423 thread_scoped=True)
2432 thread_scoped=True)
2424 with inv_context_manager as invalidation_context:
2433 with inv_context_manager as invalidation_context:
2425 args = (self.repo_id, inv_context_manager.cache_key)
2434 args = (self.repo_id, inv_context_manager.cache_key)
2426 # re-compute and store cache if we get invalidate signal
2435 # re-compute and store cache if we get invalidate signal
2427 if invalidation_context.should_invalidate():
2436 if invalidation_context.should_invalidate():
2428 instance = get_instance_cached.refresh(*args)
2437 instance = get_instance_cached.refresh(*args)
2429 else:
2438 else:
2430 instance = get_instance_cached(*args)
2439 instance = get_instance_cached(*args)
2431
2440
2432 log.debug(
2441 log.debug(
2433 'Repo instance fetched in %.3fs', inv_context_manager.compute_time)
2442 'Repo instance fetched in %.3fs', inv_context_manager.compute_time)
2434 return instance
2443 return instance
2435
2444
2436 def _get_instance(self, cache=True, config=None):
2445 def _get_instance(self, cache=True, config=None):
2437 config = config or self._config
2446 config = config or self._config
2438 custom_wire = {
2447 custom_wire = {
2439 'cache': cache # controls the vcs.remote cache
2448 'cache': cache # controls the vcs.remote cache
2440 }
2449 }
2441 repo = get_vcs_instance(
2450 repo = get_vcs_instance(
2442 repo_path=safe_str(self.repo_full_path),
2451 repo_path=safe_str(self.repo_full_path),
2443 config=config,
2452 config=config,
2444 with_wire=custom_wire,
2453 with_wire=custom_wire,
2445 create=False,
2454 create=False,
2446 _vcs_alias=self.repo_type)
2455 _vcs_alias=self.repo_type)
2447
2456
2448 return repo
2457 return repo
2449
2458
2450 def __json__(self):
2459 def __json__(self):
2451 return {'landing_rev': self.landing_rev}
2460 return {'landing_rev': self.landing_rev}
2452
2461
2453 def get_dict(self):
2462 def get_dict(self):
2454
2463
2455 # Since we transformed `repo_name` to a hybrid property, we need to
2464 # Since we transformed `repo_name` to a hybrid property, we need to
2456 # keep compatibility with the code which uses `repo_name` field.
2465 # keep compatibility with the code which uses `repo_name` field.
2457
2466
2458 result = super(Repository, self).get_dict()
2467 result = super(Repository, self).get_dict()
2459 result['repo_name'] = result.pop('_repo_name', None)
2468 result['repo_name'] = result.pop('_repo_name', None)
2460 return result
2469 return result
2461
2470
2462
2471
2463 class RepoGroup(Base, BaseModel):
2472 class RepoGroup(Base, BaseModel):
2464 __tablename__ = 'groups'
2473 __tablename__ = 'groups'
2465 __table_args__ = (
2474 __table_args__ = (
2466 UniqueConstraint('group_name', 'group_parent_id'),
2475 UniqueConstraint('group_name', 'group_parent_id'),
2467 base_table_args,
2476 base_table_args,
2468 )
2477 )
2469 __mapper_args__ = {'order_by': 'group_name'}
2478 __mapper_args__ = {'order_by': 'group_name'}
2470
2479
2471 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2480 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2472
2481
2473 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2482 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2474 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2483 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2475 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2484 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2476 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2485 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2477 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2486 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2478 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2487 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2479 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2488 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2480 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2489 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2481 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2490 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2482 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2491 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2483
2492
2484 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2493 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2485 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2494 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2486 parent_group = relationship('RepoGroup', remote_side=group_id)
2495 parent_group = relationship('RepoGroup', remote_side=group_id)
2487 user = relationship('User')
2496 user = relationship('User')
2488 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
2497 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
2489
2498
2490 def __init__(self, group_name='', parent_group=None):
2499 def __init__(self, group_name='', parent_group=None):
2491 self.group_name = group_name
2500 self.group_name = group_name
2492 self.parent_group = parent_group
2501 self.parent_group = parent_group
2493
2502
2494 def __unicode__(self):
2503 def __unicode__(self):
2495 return u"<%s('id:%s:%s')>" % (
2504 return u"<%s('id:%s:%s')>" % (
2496 self.__class__.__name__, self.group_id, self.group_name)
2505 self.__class__.__name__, self.group_id, self.group_name)
2497
2506
2498 @hybrid_property
2507 @hybrid_property
2499 def group_name(self):
2508 def group_name(self):
2500 return self._group_name
2509 return self._group_name
2501
2510
2502 @group_name.setter
2511 @group_name.setter
2503 def group_name(self, value):
2512 def group_name(self, value):
2504 self._group_name = value
2513 self._group_name = value
2505 self.group_name_hash = self.hash_repo_group_name(value)
2514 self.group_name_hash = self.hash_repo_group_name(value)
2506
2515
2507 @validates('group_parent_id')
2516 @validates('group_parent_id')
2508 def validate_group_parent_id(self, key, val):
2517 def validate_group_parent_id(self, key, val):
2509 """
2518 """
2510 Check cycle references for a parent group to self
2519 Check cycle references for a parent group to self
2511 """
2520 """
2512 if self.group_id and val:
2521 if self.group_id and val:
2513 assert val != self.group_id
2522 assert val != self.group_id
2514
2523
2515 return val
2524 return val
2516
2525
2517 @hybrid_property
2526 @hybrid_property
2518 def description_safe(self):
2527 def description_safe(self):
2519 from rhodecode.lib import helpers as h
2528 from rhodecode.lib import helpers as h
2520 return h.escape(self.group_description)
2529 return h.escape(self.group_description)
2521
2530
2522 @classmethod
2531 @classmethod
2523 def hash_repo_group_name(cls, repo_group_name):
2532 def hash_repo_group_name(cls, repo_group_name):
2524 val = remove_formatting(repo_group_name)
2533 val = remove_formatting(repo_group_name)
2525 val = safe_str(val).lower()
2534 val = safe_str(val).lower()
2526 chars = []
2535 chars = []
2527 for c in val:
2536 for c in val:
2528 if c not in string.ascii_letters:
2537 if c not in string.ascii_letters:
2529 c = str(ord(c))
2538 c = str(ord(c))
2530 chars.append(c)
2539 chars.append(c)
2531
2540
2532 return ''.join(chars)
2541 return ''.join(chars)
2533
2542
2534 @classmethod
2543 @classmethod
2535 def _generate_choice(cls, repo_group):
2544 def _generate_choice(cls, repo_group):
2536 from webhelpers.html import literal as _literal
2545 from webhelpers.html import literal as _literal
2537 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2546 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2538 return repo_group.group_id, _name(repo_group.full_path_splitted)
2547 return repo_group.group_id, _name(repo_group.full_path_splitted)
2539
2548
2540 @classmethod
2549 @classmethod
2541 def groups_choices(cls, groups=None, show_empty_group=True):
2550 def groups_choices(cls, groups=None, show_empty_group=True):
2542 if not groups:
2551 if not groups:
2543 groups = cls.query().all()
2552 groups = cls.query().all()
2544
2553
2545 repo_groups = []
2554 repo_groups = []
2546 if show_empty_group:
2555 if show_empty_group:
2547 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2556 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2548
2557
2549 repo_groups.extend([cls._generate_choice(x) for x in groups])
2558 repo_groups.extend([cls._generate_choice(x) for x in groups])
2550
2559
2551 repo_groups = sorted(
2560 repo_groups = sorted(
2552 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2561 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2553 return repo_groups
2562 return repo_groups
2554
2563
2555 @classmethod
2564 @classmethod
2556 def url_sep(cls):
2565 def url_sep(cls):
2557 return URL_SEP
2566 return URL_SEP
2558
2567
2559 @classmethod
2568 @classmethod
2560 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2569 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2561 if case_insensitive:
2570 if case_insensitive:
2562 gr = cls.query().filter(func.lower(cls.group_name)
2571 gr = cls.query().filter(func.lower(cls.group_name)
2563 == func.lower(group_name))
2572 == func.lower(group_name))
2564 else:
2573 else:
2565 gr = cls.query().filter(cls.group_name == group_name)
2574 gr = cls.query().filter(cls.group_name == group_name)
2566 if cache:
2575 if cache:
2567 name_key = _hash_key(group_name)
2576 name_key = _hash_key(group_name)
2568 gr = gr.options(
2577 gr = gr.options(
2569 FromCache("sql_cache_short", "get_group_%s" % name_key))
2578 FromCache("sql_cache_short", "get_group_%s" % name_key))
2570 return gr.scalar()
2579 return gr.scalar()
2571
2580
2572 @classmethod
2581 @classmethod
2573 def get_user_personal_repo_group(cls, user_id):
2582 def get_user_personal_repo_group(cls, user_id):
2574 user = User.get(user_id)
2583 user = User.get(user_id)
2575 if user.username == User.DEFAULT_USER:
2584 if user.username == User.DEFAULT_USER:
2576 return None
2585 return None
2577
2586
2578 return cls.query()\
2587 return cls.query()\
2579 .filter(cls.personal == true()) \
2588 .filter(cls.personal == true()) \
2580 .filter(cls.user == user) \
2589 .filter(cls.user == user) \
2581 .order_by(cls.group_id.asc()) \
2590 .order_by(cls.group_id.asc()) \
2582 .first()
2591 .first()
2583
2592
2584 @classmethod
2593 @classmethod
2585 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2594 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2586 case_insensitive=True):
2595 case_insensitive=True):
2587 q = RepoGroup.query()
2596 q = RepoGroup.query()
2588
2597
2589 if not isinstance(user_id, Optional):
2598 if not isinstance(user_id, Optional):
2590 q = q.filter(RepoGroup.user_id == user_id)
2599 q = q.filter(RepoGroup.user_id == user_id)
2591
2600
2592 if not isinstance(group_id, Optional):
2601 if not isinstance(group_id, Optional):
2593 q = q.filter(RepoGroup.group_parent_id == group_id)
2602 q = q.filter(RepoGroup.group_parent_id == group_id)
2594
2603
2595 if case_insensitive:
2604 if case_insensitive:
2596 q = q.order_by(func.lower(RepoGroup.group_name))
2605 q = q.order_by(func.lower(RepoGroup.group_name))
2597 else:
2606 else:
2598 q = q.order_by(RepoGroup.group_name)
2607 q = q.order_by(RepoGroup.group_name)
2599 return q.all()
2608 return q.all()
2600
2609
2601 @property
2610 @property
2602 def parents(self):
2611 def parents(self):
2603 parents_recursion_limit = 10
2612 parents_recursion_limit = 10
2604 groups = []
2613 groups = []
2605 if self.parent_group is None:
2614 if self.parent_group is None:
2606 return groups
2615 return groups
2607 cur_gr = self.parent_group
2616 cur_gr = self.parent_group
2608 groups.insert(0, cur_gr)
2617 groups.insert(0, cur_gr)
2609 cnt = 0
2618 cnt = 0
2610 while 1:
2619 while 1:
2611 cnt += 1
2620 cnt += 1
2612 gr = getattr(cur_gr, 'parent_group', None)
2621 gr = getattr(cur_gr, 'parent_group', None)
2613 cur_gr = cur_gr.parent_group
2622 cur_gr = cur_gr.parent_group
2614 if gr is None:
2623 if gr is None:
2615 break
2624 break
2616 if cnt == parents_recursion_limit:
2625 if cnt == parents_recursion_limit:
2617 # this will prevent accidental infinit loops
2626 # this will prevent accidental infinit loops
2618 log.error('more than %s parents found for group %s, stopping '
2627 log.error('more than %s parents found for group %s, stopping '
2619 'recursive parent fetching', parents_recursion_limit, self)
2628 'recursive parent fetching', parents_recursion_limit, self)
2620 break
2629 break
2621
2630
2622 groups.insert(0, gr)
2631 groups.insert(0, gr)
2623 return groups
2632 return groups
2624
2633
2625 @property
2634 @property
2626 def last_db_change(self):
2635 def last_db_change(self):
2627 return self.updated_on
2636 return self.updated_on
2628
2637
2629 @property
2638 @property
2630 def children(self):
2639 def children(self):
2631 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2640 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2632
2641
2633 @property
2642 @property
2634 def name(self):
2643 def name(self):
2635 return self.group_name.split(RepoGroup.url_sep())[-1]
2644 return self.group_name.split(RepoGroup.url_sep())[-1]
2636
2645
2637 @property
2646 @property
2638 def full_path(self):
2647 def full_path(self):
2639 return self.group_name
2648 return self.group_name
2640
2649
2641 @property
2650 @property
2642 def full_path_splitted(self):
2651 def full_path_splitted(self):
2643 return self.group_name.split(RepoGroup.url_sep())
2652 return self.group_name.split(RepoGroup.url_sep())
2644
2653
2645 @property
2654 @property
2646 def repositories(self):
2655 def repositories(self):
2647 return Repository.query()\
2656 return Repository.query()\
2648 .filter(Repository.group == self)\
2657 .filter(Repository.group == self)\
2649 .order_by(Repository.repo_name)
2658 .order_by(Repository.repo_name)
2650
2659
2651 @property
2660 @property
2652 def repositories_recursive_count(self):
2661 def repositories_recursive_count(self):
2653 cnt = self.repositories.count()
2662 cnt = self.repositories.count()
2654
2663
2655 def children_count(group):
2664 def children_count(group):
2656 cnt = 0
2665 cnt = 0
2657 for child in group.children:
2666 for child in group.children:
2658 cnt += child.repositories.count()
2667 cnt += child.repositories.count()
2659 cnt += children_count(child)
2668 cnt += children_count(child)
2660 return cnt
2669 return cnt
2661
2670
2662 return cnt + children_count(self)
2671 return cnt + children_count(self)
2663
2672
2664 def _recursive_objects(self, include_repos=True):
2673 def _recursive_objects(self, include_repos=True):
2665 all_ = []
2674 all_ = []
2666
2675
2667 def _get_members(root_gr):
2676 def _get_members(root_gr):
2668 if include_repos:
2677 if include_repos:
2669 for r in root_gr.repositories:
2678 for r in root_gr.repositories:
2670 all_.append(r)
2679 all_.append(r)
2671 childs = root_gr.children.all()
2680 childs = root_gr.children.all()
2672 if childs:
2681 if childs:
2673 for gr in childs:
2682 for gr in childs:
2674 all_.append(gr)
2683 all_.append(gr)
2675 _get_members(gr)
2684 _get_members(gr)
2676
2685
2677 _get_members(self)
2686 _get_members(self)
2678 return [self] + all_
2687 return [self] + all_
2679
2688
2680 def recursive_groups_and_repos(self):
2689 def recursive_groups_and_repos(self):
2681 """
2690 """
2682 Recursive return all groups, with repositories in those groups
2691 Recursive return all groups, with repositories in those groups
2683 """
2692 """
2684 return self._recursive_objects()
2693 return self._recursive_objects()
2685
2694
2686 def recursive_groups(self):
2695 def recursive_groups(self):
2687 """
2696 """
2688 Returns all children groups for this group including children of children
2697 Returns all children groups for this group including children of children
2689 """
2698 """
2690 return self._recursive_objects(include_repos=False)
2699 return self._recursive_objects(include_repos=False)
2691
2700
2692 def get_new_name(self, group_name):
2701 def get_new_name(self, group_name):
2693 """
2702 """
2694 returns new full group name based on parent and new name
2703 returns new full group name based on parent and new name
2695
2704
2696 :param group_name:
2705 :param group_name:
2697 """
2706 """
2698 path_prefix = (self.parent_group.full_path_splitted if
2707 path_prefix = (self.parent_group.full_path_splitted if
2699 self.parent_group else [])
2708 self.parent_group else [])
2700 return RepoGroup.url_sep().join(path_prefix + [group_name])
2709 return RepoGroup.url_sep().join(path_prefix + [group_name])
2701
2710
2702 def permissions(self, with_admins=True, with_owner=True,
2711 def permissions(self, with_admins=True, with_owner=True,
2703 expand_from_user_groups=False):
2712 expand_from_user_groups=False):
2704 """
2713 """
2705 Permissions for repository groups
2714 Permissions for repository groups
2706 """
2715 """
2707 _admin_perm = 'group.admin'
2716 _admin_perm = 'group.admin'
2708
2717
2709 owner_row = []
2718 owner_row = []
2710 if with_owner:
2719 if with_owner:
2711 usr = AttributeDict(self.user.get_dict())
2720 usr = AttributeDict(self.user.get_dict())
2712 usr.owner_row = True
2721 usr.owner_row = True
2713 usr.permission = _admin_perm
2722 usr.permission = _admin_perm
2714 owner_row.append(usr)
2723 owner_row.append(usr)
2715
2724
2716 super_admin_ids = []
2725 super_admin_ids = []
2717 super_admin_rows = []
2726 super_admin_rows = []
2718 if with_admins:
2727 if with_admins:
2719 for usr in User.get_all_super_admins():
2728 for usr in User.get_all_super_admins():
2720 super_admin_ids.append(usr.user_id)
2729 super_admin_ids.append(usr.user_id)
2721 # if this admin is also owner, don't double the record
2730 # if this admin is also owner, don't double the record
2722 if usr.user_id == owner_row[0].user_id:
2731 if usr.user_id == owner_row[0].user_id:
2723 owner_row[0].admin_row = True
2732 owner_row[0].admin_row = True
2724 else:
2733 else:
2725 usr = AttributeDict(usr.get_dict())
2734 usr = AttributeDict(usr.get_dict())
2726 usr.admin_row = True
2735 usr.admin_row = True
2727 usr.permission = _admin_perm
2736 usr.permission = _admin_perm
2728 super_admin_rows.append(usr)
2737 super_admin_rows.append(usr)
2729
2738
2730 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2739 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2731 q = q.options(joinedload(UserRepoGroupToPerm.group),
2740 q = q.options(joinedload(UserRepoGroupToPerm.group),
2732 joinedload(UserRepoGroupToPerm.user),
2741 joinedload(UserRepoGroupToPerm.user),
2733 joinedload(UserRepoGroupToPerm.permission),)
2742 joinedload(UserRepoGroupToPerm.permission),)
2734
2743
2735 # get owners and admins and permissions. We do a trick of re-writing
2744 # get owners and admins and permissions. We do a trick of re-writing
2736 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2745 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2737 # has a global reference and changing one object propagates to all
2746 # has a global reference and changing one object propagates to all
2738 # others. This means if admin is also an owner admin_row that change
2747 # others. This means if admin is also an owner admin_row that change
2739 # would propagate to both objects
2748 # would propagate to both objects
2740 perm_rows = []
2749 perm_rows = []
2741 for _usr in q.all():
2750 for _usr in q.all():
2742 usr = AttributeDict(_usr.user.get_dict())
2751 usr = AttributeDict(_usr.user.get_dict())
2743 # if this user is also owner/admin, mark as duplicate record
2752 # if this user is also owner/admin, mark as duplicate record
2744 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2753 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2745 usr.duplicate_perm = True
2754 usr.duplicate_perm = True
2746 usr.permission = _usr.permission.permission_name
2755 usr.permission = _usr.permission.permission_name
2747 perm_rows.append(usr)
2756 perm_rows.append(usr)
2748
2757
2749 # filter the perm rows by 'default' first and then sort them by
2758 # filter the perm rows by 'default' first and then sort them by
2750 # admin,write,read,none permissions sorted again alphabetically in
2759 # admin,write,read,none permissions sorted again alphabetically in
2751 # each group
2760 # each group
2752 perm_rows = sorted(perm_rows, key=display_user_sort)
2761 perm_rows = sorted(perm_rows, key=display_user_sort)
2753
2762
2754 user_groups_rows = []
2763 user_groups_rows = []
2755 if expand_from_user_groups:
2764 if expand_from_user_groups:
2756 for ug in self.permission_user_groups(with_members=True):
2765 for ug in self.permission_user_groups(with_members=True):
2757 for user_data in ug.members:
2766 for user_data in ug.members:
2758 user_groups_rows.append(user_data)
2767 user_groups_rows.append(user_data)
2759
2768
2760 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2769 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2761
2770
2762 def permission_user_groups(self, with_members=False):
2771 def permission_user_groups(self, with_members=False):
2763 q = UserGroupRepoGroupToPerm.query()\
2772 q = UserGroupRepoGroupToPerm.query()\
2764 .filter(UserGroupRepoGroupToPerm.group == self)
2773 .filter(UserGroupRepoGroupToPerm.group == self)
2765 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2774 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2766 joinedload(UserGroupRepoGroupToPerm.users_group),
2775 joinedload(UserGroupRepoGroupToPerm.users_group),
2767 joinedload(UserGroupRepoGroupToPerm.permission),)
2776 joinedload(UserGroupRepoGroupToPerm.permission),)
2768
2777
2769 perm_rows = []
2778 perm_rows = []
2770 for _user_group in q.all():
2779 for _user_group in q.all():
2771 entry = AttributeDict(_user_group.users_group.get_dict())
2780 entry = AttributeDict(_user_group.users_group.get_dict())
2772 entry.permission = _user_group.permission.permission_name
2781 entry.permission = _user_group.permission.permission_name
2773 if with_members:
2782 if with_members:
2774 entry.members = [x.user.get_dict()
2783 entry.members = [x.user.get_dict()
2775 for x in _user_group.users_group.members]
2784 for x in _user_group.users_group.members]
2776 perm_rows.append(entry)
2785 perm_rows.append(entry)
2777
2786
2778 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2787 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2779 return perm_rows
2788 return perm_rows
2780
2789
2781 def get_api_data(self):
2790 def get_api_data(self):
2782 """
2791 """
2783 Common function for generating api data
2792 Common function for generating api data
2784
2793
2785 """
2794 """
2786 group = self
2795 group = self
2787 data = {
2796 data = {
2788 'group_id': group.group_id,
2797 'group_id': group.group_id,
2789 'group_name': group.group_name,
2798 'group_name': group.group_name,
2790 'group_description': group.description_safe,
2799 'group_description': group.description_safe,
2791 'parent_group': group.parent_group.group_name if group.parent_group else None,
2800 'parent_group': group.parent_group.group_name if group.parent_group else None,
2792 'repositories': [x.repo_name for x in group.repositories],
2801 'repositories': [x.repo_name for x in group.repositories],
2793 'owner': group.user.username,
2802 'owner': group.user.username,
2794 }
2803 }
2795 return data
2804 return data
2796
2805
2797 def get_dict(self):
2806 def get_dict(self):
2798 # Since we transformed `group_name` to a hybrid property, we need to
2807 # Since we transformed `group_name` to a hybrid property, we need to
2799 # keep compatibility with the code which uses `group_name` field.
2808 # keep compatibility with the code which uses `group_name` field.
2800 result = super(RepoGroup, self).get_dict()
2809 result = super(RepoGroup, self).get_dict()
2801 result['group_name'] = result.pop('_group_name', None)
2810 result['group_name'] = result.pop('_group_name', None)
2802 return result
2811 return result
2803
2812
2804
2813
2805 class Permission(Base, BaseModel):
2814 class Permission(Base, BaseModel):
2806 __tablename__ = 'permissions'
2815 __tablename__ = 'permissions'
2807 __table_args__ = (
2816 __table_args__ = (
2808 Index('p_perm_name_idx', 'permission_name'),
2817 Index('p_perm_name_idx', 'permission_name'),
2809 base_table_args,
2818 base_table_args,
2810 )
2819 )
2811
2820
2812 PERMS = [
2821 PERMS = [
2813 ('hg.admin', _('RhodeCode Super Administrator')),
2822 ('hg.admin', _('RhodeCode Super Administrator')),
2814
2823
2815 ('repository.none', _('Repository no access')),
2824 ('repository.none', _('Repository no access')),
2816 ('repository.read', _('Repository read access')),
2825 ('repository.read', _('Repository read access')),
2817 ('repository.write', _('Repository write access')),
2826 ('repository.write', _('Repository write access')),
2818 ('repository.admin', _('Repository admin access')),
2827 ('repository.admin', _('Repository admin access')),
2819
2828
2820 ('group.none', _('Repository group no access')),
2829 ('group.none', _('Repository group no access')),
2821 ('group.read', _('Repository group read access')),
2830 ('group.read', _('Repository group read access')),
2822 ('group.write', _('Repository group write access')),
2831 ('group.write', _('Repository group write access')),
2823 ('group.admin', _('Repository group admin access')),
2832 ('group.admin', _('Repository group admin access')),
2824
2833
2825 ('usergroup.none', _('User group no access')),
2834 ('usergroup.none', _('User group no access')),
2826 ('usergroup.read', _('User group read access')),
2835 ('usergroup.read', _('User group read access')),
2827 ('usergroup.write', _('User group write access')),
2836 ('usergroup.write', _('User group write access')),
2828 ('usergroup.admin', _('User group admin access')),
2837 ('usergroup.admin', _('User group admin access')),
2829
2838
2830 ('branch.none', _('Branch no permissions')),
2839 ('branch.none', _('Branch no permissions')),
2831 ('branch.merge', _('Branch access by web merge')),
2840 ('branch.merge', _('Branch access by web merge')),
2832 ('branch.push', _('Branch access by push')),
2841 ('branch.push', _('Branch access by push')),
2833 ('branch.push_force', _('Branch access by push with force')),
2842 ('branch.push_force', _('Branch access by push with force')),
2834
2843
2835 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2844 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2836 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2845 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2837
2846
2838 ('hg.usergroup.create.false', _('User Group creation disabled')),
2847 ('hg.usergroup.create.false', _('User Group creation disabled')),
2839 ('hg.usergroup.create.true', _('User Group creation enabled')),
2848 ('hg.usergroup.create.true', _('User Group creation enabled')),
2840
2849
2841 ('hg.create.none', _('Repository creation disabled')),
2850 ('hg.create.none', _('Repository creation disabled')),
2842 ('hg.create.repository', _('Repository creation enabled')),
2851 ('hg.create.repository', _('Repository creation enabled')),
2843 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2852 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2844 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2853 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2845
2854
2846 ('hg.fork.none', _('Repository forking disabled')),
2855 ('hg.fork.none', _('Repository forking disabled')),
2847 ('hg.fork.repository', _('Repository forking enabled')),
2856 ('hg.fork.repository', _('Repository forking enabled')),
2848
2857
2849 ('hg.register.none', _('Registration disabled')),
2858 ('hg.register.none', _('Registration disabled')),
2850 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2859 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2851 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2860 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2852
2861
2853 ('hg.password_reset.enabled', _('Password reset enabled')),
2862 ('hg.password_reset.enabled', _('Password reset enabled')),
2854 ('hg.password_reset.hidden', _('Password reset hidden')),
2863 ('hg.password_reset.hidden', _('Password reset hidden')),
2855 ('hg.password_reset.disabled', _('Password reset disabled')),
2864 ('hg.password_reset.disabled', _('Password reset disabled')),
2856
2865
2857 ('hg.extern_activate.manual', _('Manual activation of external account')),
2866 ('hg.extern_activate.manual', _('Manual activation of external account')),
2858 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2867 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2859
2868
2860 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2869 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2861 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2870 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2862 ]
2871 ]
2863
2872
2864 # definition of system default permissions for DEFAULT user, created on
2873 # definition of system default permissions for DEFAULT user, created on
2865 # system setup
2874 # system setup
2866 DEFAULT_USER_PERMISSIONS = [
2875 DEFAULT_USER_PERMISSIONS = [
2867 # object perms
2876 # object perms
2868 'repository.read',
2877 'repository.read',
2869 'group.read',
2878 'group.read',
2870 'usergroup.read',
2879 'usergroup.read',
2871 # branch, for backward compat we need same value as before so forced pushed
2880 # branch, for backward compat we need same value as before so forced pushed
2872 'branch.push_force',
2881 'branch.push_force',
2873 # global
2882 # global
2874 'hg.create.repository',
2883 'hg.create.repository',
2875 'hg.repogroup.create.false',
2884 'hg.repogroup.create.false',
2876 'hg.usergroup.create.false',
2885 'hg.usergroup.create.false',
2877 'hg.create.write_on_repogroup.true',
2886 'hg.create.write_on_repogroup.true',
2878 'hg.fork.repository',
2887 'hg.fork.repository',
2879 'hg.register.manual_activate',
2888 'hg.register.manual_activate',
2880 'hg.password_reset.enabled',
2889 'hg.password_reset.enabled',
2881 'hg.extern_activate.auto',
2890 'hg.extern_activate.auto',
2882 'hg.inherit_default_perms.true',
2891 'hg.inherit_default_perms.true',
2883 ]
2892 ]
2884
2893
2885 # defines which permissions are more important higher the more important
2894 # defines which permissions are more important higher the more important
2886 # Weight defines which permissions are more important.
2895 # Weight defines which permissions are more important.
2887 # The higher number the more important.
2896 # The higher number the more important.
2888 PERM_WEIGHTS = {
2897 PERM_WEIGHTS = {
2889 'repository.none': 0,
2898 'repository.none': 0,
2890 'repository.read': 1,
2899 'repository.read': 1,
2891 'repository.write': 3,
2900 'repository.write': 3,
2892 'repository.admin': 4,
2901 'repository.admin': 4,
2893
2902
2894 'group.none': 0,
2903 'group.none': 0,
2895 'group.read': 1,
2904 'group.read': 1,
2896 'group.write': 3,
2905 'group.write': 3,
2897 'group.admin': 4,
2906 'group.admin': 4,
2898
2907
2899 'usergroup.none': 0,
2908 'usergroup.none': 0,
2900 'usergroup.read': 1,
2909 'usergroup.read': 1,
2901 'usergroup.write': 3,
2910 'usergroup.write': 3,
2902 'usergroup.admin': 4,
2911 'usergroup.admin': 4,
2903
2912
2904 'branch.none': 0,
2913 'branch.none': 0,
2905 'branch.merge': 1,
2914 'branch.merge': 1,
2906 'branch.push': 3,
2915 'branch.push': 3,
2907 'branch.push_force': 4,
2916 'branch.push_force': 4,
2908
2917
2909 'hg.repogroup.create.false': 0,
2918 'hg.repogroup.create.false': 0,
2910 'hg.repogroup.create.true': 1,
2919 'hg.repogroup.create.true': 1,
2911
2920
2912 'hg.usergroup.create.false': 0,
2921 'hg.usergroup.create.false': 0,
2913 'hg.usergroup.create.true': 1,
2922 'hg.usergroup.create.true': 1,
2914
2923
2915 'hg.fork.none': 0,
2924 'hg.fork.none': 0,
2916 'hg.fork.repository': 1,
2925 'hg.fork.repository': 1,
2917 'hg.create.none': 0,
2926 'hg.create.none': 0,
2918 'hg.create.repository': 1
2927 'hg.create.repository': 1
2919 }
2928 }
2920
2929
2921 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2930 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2922 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2931 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2923 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2932 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2924
2933
2925 def __unicode__(self):
2934 def __unicode__(self):
2926 return u"<%s('%s:%s')>" % (
2935 return u"<%s('%s:%s')>" % (
2927 self.__class__.__name__, self.permission_id, self.permission_name
2936 self.__class__.__name__, self.permission_id, self.permission_name
2928 )
2937 )
2929
2938
2930 @classmethod
2939 @classmethod
2931 def get_by_key(cls, key):
2940 def get_by_key(cls, key):
2932 return cls.query().filter(cls.permission_name == key).scalar()
2941 return cls.query().filter(cls.permission_name == key).scalar()
2933
2942
2934 @classmethod
2943 @classmethod
2935 def get_default_repo_perms(cls, user_id, repo_id=None):
2944 def get_default_repo_perms(cls, user_id, repo_id=None):
2936 q = Session().query(UserRepoToPerm, Repository, Permission)\
2945 q = Session().query(UserRepoToPerm, Repository, Permission)\
2937 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2946 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2938 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2947 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2939 .filter(UserRepoToPerm.user_id == user_id)
2948 .filter(UserRepoToPerm.user_id == user_id)
2940 if repo_id:
2949 if repo_id:
2941 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2950 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2942 return q.all()
2951 return q.all()
2943
2952
2944 @classmethod
2953 @classmethod
2945 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
2954 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
2946 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
2955 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
2947 .join(
2956 .join(
2948 Permission,
2957 Permission,
2949 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
2958 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
2950 .join(
2959 .join(
2951 UserRepoToPerm,
2960 UserRepoToPerm,
2952 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
2961 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
2953 .filter(UserRepoToPerm.user_id == user_id)
2962 .filter(UserRepoToPerm.user_id == user_id)
2954
2963
2955 if repo_id:
2964 if repo_id:
2956 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
2965 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
2957 return q.order_by(UserToRepoBranchPermission.rule_order).all()
2966 return q.order_by(UserToRepoBranchPermission.rule_order).all()
2958
2967
2959 @classmethod
2968 @classmethod
2960 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2969 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2961 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2970 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2962 .join(
2971 .join(
2963 Permission,
2972 Permission,
2964 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2973 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2965 .join(
2974 .join(
2966 Repository,
2975 Repository,
2967 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2976 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2968 .join(
2977 .join(
2969 UserGroup,
2978 UserGroup,
2970 UserGroupRepoToPerm.users_group_id ==
2979 UserGroupRepoToPerm.users_group_id ==
2971 UserGroup.users_group_id)\
2980 UserGroup.users_group_id)\
2972 .join(
2981 .join(
2973 UserGroupMember,
2982 UserGroupMember,
2974 UserGroupRepoToPerm.users_group_id ==
2983 UserGroupRepoToPerm.users_group_id ==
2975 UserGroupMember.users_group_id)\
2984 UserGroupMember.users_group_id)\
2976 .filter(
2985 .filter(
2977 UserGroupMember.user_id == user_id,
2986 UserGroupMember.user_id == user_id,
2978 UserGroup.users_group_active == true())
2987 UserGroup.users_group_active == true())
2979 if repo_id:
2988 if repo_id:
2980 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2989 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2981 return q.all()
2990 return q.all()
2982
2991
2983 @classmethod
2992 @classmethod
2984 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
2993 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
2985 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
2994 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
2986 .join(
2995 .join(
2987 Permission,
2996 Permission,
2988 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
2997 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
2989 .join(
2998 .join(
2990 UserGroupRepoToPerm,
2999 UserGroupRepoToPerm,
2991 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3000 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
2992 .join(
3001 .join(
2993 UserGroup,
3002 UserGroup,
2994 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3003 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
2995 .join(
3004 .join(
2996 UserGroupMember,
3005 UserGroupMember,
2997 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3006 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
2998 .filter(
3007 .filter(
2999 UserGroupMember.user_id == user_id,
3008 UserGroupMember.user_id == user_id,
3000 UserGroup.users_group_active == true())
3009 UserGroup.users_group_active == true())
3001
3010
3002 if repo_id:
3011 if repo_id:
3003 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3012 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3004 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3013 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3005
3014
3006 @classmethod
3015 @classmethod
3007 def get_default_group_perms(cls, user_id, repo_group_id=None):
3016 def get_default_group_perms(cls, user_id, repo_group_id=None):
3008 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3017 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3009 .join(
3018 .join(
3010 Permission,
3019 Permission,
3011 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3020 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3012 .join(
3021 .join(
3013 RepoGroup,
3022 RepoGroup,
3014 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3023 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3015 .filter(UserRepoGroupToPerm.user_id == user_id)
3024 .filter(UserRepoGroupToPerm.user_id == user_id)
3016 if repo_group_id:
3025 if repo_group_id:
3017 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3026 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3018 return q.all()
3027 return q.all()
3019
3028
3020 @classmethod
3029 @classmethod
3021 def get_default_group_perms_from_user_group(
3030 def get_default_group_perms_from_user_group(
3022 cls, user_id, repo_group_id=None):
3031 cls, user_id, repo_group_id=None):
3023 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3032 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3024 .join(
3033 .join(
3025 Permission,
3034 Permission,
3026 UserGroupRepoGroupToPerm.permission_id ==
3035 UserGroupRepoGroupToPerm.permission_id ==
3027 Permission.permission_id)\
3036 Permission.permission_id)\
3028 .join(
3037 .join(
3029 RepoGroup,
3038 RepoGroup,
3030 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3039 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3031 .join(
3040 .join(
3032 UserGroup,
3041 UserGroup,
3033 UserGroupRepoGroupToPerm.users_group_id ==
3042 UserGroupRepoGroupToPerm.users_group_id ==
3034 UserGroup.users_group_id)\
3043 UserGroup.users_group_id)\
3035 .join(
3044 .join(
3036 UserGroupMember,
3045 UserGroupMember,
3037 UserGroupRepoGroupToPerm.users_group_id ==
3046 UserGroupRepoGroupToPerm.users_group_id ==
3038 UserGroupMember.users_group_id)\
3047 UserGroupMember.users_group_id)\
3039 .filter(
3048 .filter(
3040 UserGroupMember.user_id == user_id,
3049 UserGroupMember.user_id == user_id,
3041 UserGroup.users_group_active == true())
3050 UserGroup.users_group_active == true())
3042 if repo_group_id:
3051 if repo_group_id:
3043 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3052 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3044 return q.all()
3053 return q.all()
3045
3054
3046 @classmethod
3055 @classmethod
3047 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3056 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3048 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3057 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3049 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3058 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3050 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3059 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3051 .filter(UserUserGroupToPerm.user_id == user_id)
3060 .filter(UserUserGroupToPerm.user_id == user_id)
3052 if user_group_id:
3061 if user_group_id:
3053 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3062 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3054 return q.all()
3063 return q.all()
3055
3064
3056 @classmethod
3065 @classmethod
3057 def get_default_user_group_perms_from_user_group(
3066 def get_default_user_group_perms_from_user_group(
3058 cls, user_id, user_group_id=None):
3067 cls, user_id, user_group_id=None):
3059 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3068 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3060 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3069 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3061 .join(
3070 .join(
3062 Permission,
3071 Permission,
3063 UserGroupUserGroupToPerm.permission_id ==
3072 UserGroupUserGroupToPerm.permission_id ==
3064 Permission.permission_id)\
3073 Permission.permission_id)\
3065 .join(
3074 .join(
3066 TargetUserGroup,
3075 TargetUserGroup,
3067 UserGroupUserGroupToPerm.target_user_group_id ==
3076 UserGroupUserGroupToPerm.target_user_group_id ==
3068 TargetUserGroup.users_group_id)\
3077 TargetUserGroup.users_group_id)\
3069 .join(
3078 .join(
3070 UserGroup,
3079 UserGroup,
3071 UserGroupUserGroupToPerm.user_group_id ==
3080 UserGroupUserGroupToPerm.user_group_id ==
3072 UserGroup.users_group_id)\
3081 UserGroup.users_group_id)\
3073 .join(
3082 .join(
3074 UserGroupMember,
3083 UserGroupMember,
3075 UserGroupUserGroupToPerm.user_group_id ==
3084 UserGroupUserGroupToPerm.user_group_id ==
3076 UserGroupMember.users_group_id)\
3085 UserGroupMember.users_group_id)\
3077 .filter(
3086 .filter(
3078 UserGroupMember.user_id == user_id,
3087 UserGroupMember.user_id == user_id,
3079 UserGroup.users_group_active == true())
3088 UserGroup.users_group_active == true())
3080 if user_group_id:
3089 if user_group_id:
3081 q = q.filter(
3090 q = q.filter(
3082 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3091 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3083
3092
3084 return q.all()
3093 return q.all()
3085
3094
3086
3095
3087 class UserRepoToPerm(Base, BaseModel):
3096 class UserRepoToPerm(Base, BaseModel):
3088 __tablename__ = 'repo_to_perm'
3097 __tablename__ = 'repo_to_perm'
3089 __table_args__ = (
3098 __table_args__ = (
3090 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3099 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3091 base_table_args
3100 base_table_args
3092 )
3101 )
3093
3102
3094 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3103 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3095 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3104 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3096 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3105 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3097 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3106 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3098
3107
3099 user = relationship('User')
3108 user = relationship('User')
3100 repository = relationship('Repository')
3109 repository = relationship('Repository')
3101 permission = relationship('Permission')
3110 permission = relationship('Permission')
3102
3111
3103 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3112 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3104
3113
3105 @classmethod
3114 @classmethod
3106 def create(cls, user, repository, permission):
3115 def create(cls, user, repository, permission):
3107 n = cls()
3116 n = cls()
3108 n.user = user
3117 n.user = user
3109 n.repository = repository
3118 n.repository = repository
3110 n.permission = permission
3119 n.permission = permission
3111 Session().add(n)
3120 Session().add(n)
3112 return n
3121 return n
3113
3122
3114 def __unicode__(self):
3123 def __unicode__(self):
3115 return u'<%s => %s >' % (self.user, self.repository)
3124 return u'<%s => %s >' % (self.user, self.repository)
3116
3125
3117
3126
3118 class UserUserGroupToPerm(Base, BaseModel):
3127 class UserUserGroupToPerm(Base, BaseModel):
3119 __tablename__ = 'user_user_group_to_perm'
3128 __tablename__ = 'user_user_group_to_perm'
3120 __table_args__ = (
3129 __table_args__ = (
3121 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3130 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3122 base_table_args
3131 base_table_args
3123 )
3132 )
3124
3133
3125 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3134 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3126 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3135 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3127 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3136 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3128 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3137 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3129
3138
3130 user = relationship('User')
3139 user = relationship('User')
3131 user_group = relationship('UserGroup')
3140 user_group = relationship('UserGroup')
3132 permission = relationship('Permission')
3141 permission = relationship('Permission')
3133
3142
3134 @classmethod
3143 @classmethod
3135 def create(cls, user, user_group, permission):
3144 def create(cls, user, user_group, permission):
3136 n = cls()
3145 n = cls()
3137 n.user = user
3146 n.user = user
3138 n.user_group = user_group
3147 n.user_group = user_group
3139 n.permission = permission
3148 n.permission = permission
3140 Session().add(n)
3149 Session().add(n)
3141 return n
3150 return n
3142
3151
3143 def __unicode__(self):
3152 def __unicode__(self):
3144 return u'<%s => %s >' % (self.user, self.user_group)
3153 return u'<%s => %s >' % (self.user, self.user_group)
3145
3154
3146
3155
3147 class UserToPerm(Base, BaseModel):
3156 class UserToPerm(Base, BaseModel):
3148 __tablename__ = 'user_to_perm'
3157 __tablename__ = 'user_to_perm'
3149 __table_args__ = (
3158 __table_args__ = (
3150 UniqueConstraint('user_id', 'permission_id'),
3159 UniqueConstraint('user_id', 'permission_id'),
3151 base_table_args
3160 base_table_args
3152 )
3161 )
3153
3162
3154 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3163 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3155 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3164 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3156 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3165 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3157
3166
3158 user = relationship('User')
3167 user = relationship('User')
3159 permission = relationship('Permission', lazy='joined')
3168 permission = relationship('Permission', lazy='joined')
3160
3169
3161 def __unicode__(self):
3170 def __unicode__(self):
3162 return u'<%s => %s >' % (self.user, self.permission)
3171 return u'<%s => %s >' % (self.user, self.permission)
3163
3172
3164
3173
3165 class UserGroupRepoToPerm(Base, BaseModel):
3174 class UserGroupRepoToPerm(Base, BaseModel):
3166 __tablename__ = 'users_group_repo_to_perm'
3175 __tablename__ = 'users_group_repo_to_perm'
3167 __table_args__ = (
3176 __table_args__ = (
3168 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3177 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3169 base_table_args
3178 base_table_args
3170 )
3179 )
3171
3180
3172 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3181 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3173 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3182 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3174 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3183 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3175 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3184 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3176
3185
3177 users_group = relationship('UserGroup')
3186 users_group = relationship('UserGroup')
3178 permission = relationship('Permission')
3187 permission = relationship('Permission')
3179 repository = relationship('Repository')
3188 repository = relationship('Repository')
3180 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3189 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3181
3190
3182 @classmethod
3191 @classmethod
3183 def create(cls, users_group, repository, permission):
3192 def create(cls, users_group, repository, permission):
3184 n = cls()
3193 n = cls()
3185 n.users_group = users_group
3194 n.users_group = users_group
3186 n.repository = repository
3195 n.repository = repository
3187 n.permission = permission
3196 n.permission = permission
3188 Session().add(n)
3197 Session().add(n)
3189 return n
3198 return n
3190
3199
3191 def __unicode__(self):
3200 def __unicode__(self):
3192 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3201 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3193
3202
3194
3203
3195 class UserGroupUserGroupToPerm(Base, BaseModel):
3204 class UserGroupUserGroupToPerm(Base, BaseModel):
3196 __tablename__ = 'user_group_user_group_to_perm'
3205 __tablename__ = 'user_group_user_group_to_perm'
3197 __table_args__ = (
3206 __table_args__ = (
3198 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3207 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3199 CheckConstraint('target_user_group_id != user_group_id'),
3208 CheckConstraint('target_user_group_id != user_group_id'),
3200 base_table_args
3209 base_table_args
3201 )
3210 )
3202
3211
3203 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3212 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3204 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3213 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3205 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3214 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3206 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3215 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3207
3216
3208 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3217 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3209 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3218 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3210 permission = relationship('Permission')
3219 permission = relationship('Permission')
3211
3220
3212 @classmethod
3221 @classmethod
3213 def create(cls, target_user_group, user_group, permission):
3222 def create(cls, target_user_group, user_group, permission):
3214 n = cls()
3223 n = cls()
3215 n.target_user_group = target_user_group
3224 n.target_user_group = target_user_group
3216 n.user_group = user_group
3225 n.user_group = user_group
3217 n.permission = permission
3226 n.permission = permission
3218 Session().add(n)
3227 Session().add(n)
3219 return n
3228 return n
3220
3229
3221 def __unicode__(self):
3230 def __unicode__(self):
3222 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3231 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3223
3232
3224
3233
3225 class UserGroupToPerm(Base, BaseModel):
3234 class UserGroupToPerm(Base, BaseModel):
3226 __tablename__ = 'users_group_to_perm'
3235 __tablename__ = 'users_group_to_perm'
3227 __table_args__ = (
3236 __table_args__ = (
3228 UniqueConstraint('users_group_id', 'permission_id',),
3237 UniqueConstraint('users_group_id', 'permission_id',),
3229 base_table_args
3238 base_table_args
3230 )
3239 )
3231
3240
3232 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3241 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3233 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3242 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3234 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3243 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3235
3244
3236 users_group = relationship('UserGroup')
3245 users_group = relationship('UserGroup')
3237 permission = relationship('Permission')
3246 permission = relationship('Permission')
3238
3247
3239
3248
3240 class UserRepoGroupToPerm(Base, BaseModel):
3249 class UserRepoGroupToPerm(Base, BaseModel):
3241 __tablename__ = 'user_repo_group_to_perm'
3250 __tablename__ = 'user_repo_group_to_perm'
3242 __table_args__ = (
3251 __table_args__ = (
3243 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3252 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3244 base_table_args
3253 base_table_args
3245 )
3254 )
3246
3255
3247 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3256 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3248 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3257 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3249 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3258 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3250 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3259 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3251
3260
3252 user = relationship('User')
3261 user = relationship('User')
3253 group = relationship('RepoGroup')
3262 group = relationship('RepoGroup')
3254 permission = relationship('Permission')
3263 permission = relationship('Permission')
3255
3264
3256 @classmethod
3265 @classmethod
3257 def create(cls, user, repository_group, permission):
3266 def create(cls, user, repository_group, permission):
3258 n = cls()
3267 n = cls()
3259 n.user = user
3268 n.user = user
3260 n.group = repository_group
3269 n.group = repository_group
3261 n.permission = permission
3270 n.permission = permission
3262 Session().add(n)
3271 Session().add(n)
3263 return n
3272 return n
3264
3273
3265
3274
3266 class UserGroupRepoGroupToPerm(Base, BaseModel):
3275 class UserGroupRepoGroupToPerm(Base, BaseModel):
3267 __tablename__ = 'users_group_repo_group_to_perm'
3276 __tablename__ = 'users_group_repo_group_to_perm'
3268 __table_args__ = (
3277 __table_args__ = (
3269 UniqueConstraint('users_group_id', 'group_id'),
3278 UniqueConstraint('users_group_id', 'group_id'),
3270 base_table_args
3279 base_table_args
3271 )
3280 )
3272
3281
3273 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3282 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3274 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3283 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3275 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3284 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3276 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3285 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3277
3286
3278 users_group = relationship('UserGroup')
3287 users_group = relationship('UserGroup')
3279 permission = relationship('Permission')
3288 permission = relationship('Permission')
3280 group = relationship('RepoGroup')
3289 group = relationship('RepoGroup')
3281
3290
3282 @classmethod
3291 @classmethod
3283 def create(cls, user_group, repository_group, permission):
3292 def create(cls, user_group, repository_group, permission):
3284 n = cls()
3293 n = cls()
3285 n.users_group = user_group
3294 n.users_group = user_group
3286 n.group = repository_group
3295 n.group = repository_group
3287 n.permission = permission
3296 n.permission = permission
3288 Session().add(n)
3297 Session().add(n)
3289 return n
3298 return n
3290
3299
3291 def __unicode__(self):
3300 def __unicode__(self):
3292 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3301 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3293
3302
3294
3303
3295 class Statistics(Base, BaseModel):
3304 class Statistics(Base, BaseModel):
3296 __tablename__ = 'statistics'
3305 __tablename__ = 'statistics'
3297 __table_args__ = (
3306 __table_args__ = (
3298 base_table_args
3307 base_table_args
3299 )
3308 )
3300
3309
3301 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3310 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3302 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3311 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3303 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3312 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3304 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3313 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3305 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3314 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3306 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3315 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3307
3316
3308 repository = relationship('Repository', single_parent=True)
3317 repository = relationship('Repository', single_parent=True)
3309
3318
3310
3319
3311 class UserFollowing(Base, BaseModel):
3320 class UserFollowing(Base, BaseModel):
3312 __tablename__ = 'user_followings'
3321 __tablename__ = 'user_followings'
3313 __table_args__ = (
3322 __table_args__ = (
3314 UniqueConstraint('user_id', 'follows_repository_id'),
3323 UniqueConstraint('user_id', 'follows_repository_id'),
3315 UniqueConstraint('user_id', 'follows_user_id'),
3324 UniqueConstraint('user_id', 'follows_user_id'),
3316 base_table_args
3325 base_table_args
3317 )
3326 )
3318
3327
3319 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3328 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3320 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3329 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3321 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3330 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3322 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3331 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3323 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3332 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3324
3333
3325 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3334 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3326
3335
3327 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3336 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3328 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3337 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3329
3338
3330 @classmethod
3339 @classmethod
3331 def get_repo_followers(cls, repo_id):
3340 def get_repo_followers(cls, repo_id):
3332 return cls.query().filter(cls.follows_repo_id == repo_id)
3341 return cls.query().filter(cls.follows_repo_id == repo_id)
3333
3342
3334
3343
3335 class CacheKey(Base, BaseModel):
3344 class CacheKey(Base, BaseModel):
3336 __tablename__ = 'cache_invalidation'
3345 __tablename__ = 'cache_invalidation'
3337 __table_args__ = (
3346 __table_args__ = (
3338 UniqueConstraint('cache_key'),
3347 UniqueConstraint('cache_key'),
3339 Index('key_idx', 'cache_key'),
3348 Index('key_idx', 'cache_key'),
3340 base_table_args,
3349 base_table_args,
3341 )
3350 )
3342
3351
3343 CACHE_TYPE_FEED = 'FEED'
3352 CACHE_TYPE_FEED = 'FEED'
3344 CACHE_TYPE_README = 'README'
3353 CACHE_TYPE_README = 'README'
3345 # namespaces used to register process/thread aware caches
3354 # namespaces used to register process/thread aware caches
3346 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3355 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3347 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3356 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3348
3357
3349 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3358 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3350 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3359 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3351 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3360 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3352 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3361 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3353
3362
3354 def __init__(self, cache_key, cache_args=''):
3363 def __init__(self, cache_key, cache_args=''):
3355 self.cache_key = cache_key
3364 self.cache_key = cache_key
3356 self.cache_args = cache_args
3365 self.cache_args = cache_args
3357 self.cache_active = False
3366 self.cache_active = False
3358
3367
3359 def __unicode__(self):
3368 def __unicode__(self):
3360 return u"<%s('%s:%s[%s]')>" % (
3369 return u"<%s('%s:%s[%s]')>" % (
3361 self.__class__.__name__,
3370 self.__class__.__name__,
3362 self.cache_id, self.cache_key, self.cache_active)
3371 self.cache_id, self.cache_key, self.cache_active)
3363
3372
3364 def _cache_key_partition(self):
3373 def _cache_key_partition(self):
3365 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3374 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3366 return prefix, repo_name, suffix
3375 return prefix, repo_name, suffix
3367
3376
3368 def get_prefix(self):
3377 def get_prefix(self):
3369 """
3378 """
3370 Try to extract prefix from existing cache key. The key could consist
3379 Try to extract prefix from existing cache key. The key could consist
3371 of prefix, repo_name, suffix
3380 of prefix, repo_name, suffix
3372 """
3381 """
3373 # this returns prefix, repo_name, suffix
3382 # this returns prefix, repo_name, suffix
3374 return self._cache_key_partition()[0]
3383 return self._cache_key_partition()[0]
3375
3384
3376 def get_suffix(self):
3385 def get_suffix(self):
3377 """
3386 """
3378 get suffix that might have been used in _get_cache_key to
3387 get suffix that might have been used in _get_cache_key to
3379 generate self.cache_key. Only used for informational purposes
3388 generate self.cache_key. Only used for informational purposes
3380 in repo_edit.mako.
3389 in repo_edit.mako.
3381 """
3390 """
3382 # prefix, repo_name, suffix
3391 # prefix, repo_name, suffix
3383 return self._cache_key_partition()[2]
3392 return self._cache_key_partition()[2]
3384
3393
3385 @classmethod
3394 @classmethod
3386 def delete_all_cache(cls):
3395 def delete_all_cache(cls):
3387 """
3396 """
3388 Delete all cache keys from database.
3397 Delete all cache keys from database.
3389 Should only be run when all instances are down and all entries
3398 Should only be run when all instances are down and all entries
3390 thus stale.
3399 thus stale.
3391 """
3400 """
3392 cls.query().delete()
3401 cls.query().delete()
3393 Session().commit()
3402 Session().commit()
3394
3403
3395 @classmethod
3404 @classmethod
3396 def set_invalidate(cls, cache_uid, delete=False):
3405 def set_invalidate(cls, cache_uid, delete=False):
3397 """
3406 """
3398 Mark all caches of a repo as invalid in the database.
3407 Mark all caches of a repo as invalid in the database.
3399 """
3408 """
3400
3409
3401 try:
3410 try:
3402 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3411 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3403 if delete:
3412 if delete:
3404 qry.delete()
3413 qry.delete()
3405 log.debug('cache objects deleted for cache args %s',
3414 log.debug('cache objects deleted for cache args %s',
3406 safe_str(cache_uid))
3415 safe_str(cache_uid))
3407 else:
3416 else:
3408 qry.update({"cache_active": False})
3417 qry.update({"cache_active": False})
3409 log.debug('cache objects marked as invalid for cache args %s',
3418 log.debug('cache objects marked as invalid for cache args %s',
3410 safe_str(cache_uid))
3419 safe_str(cache_uid))
3411
3420
3412 Session().commit()
3421 Session().commit()
3413 except Exception:
3422 except Exception:
3414 log.exception(
3423 log.exception(
3415 'Cache key invalidation failed for cache args %s',
3424 'Cache key invalidation failed for cache args %s',
3416 safe_str(cache_uid))
3425 safe_str(cache_uid))
3417 Session().rollback()
3426 Session().rollback()
3418
3427
3419 @classmethod
3428 @classmethod
3420 def get_active_cache(cls, cache_key):
3429 def get_active_cache(cls, cache_key):
3421 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3430 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3422 if inv_obj:
3431 if inv_obj:
3423 return inv_obj
3432 return inv_obj
3424 return None
3433 return None
3425
3434
3426
3435
3427 class ChangesetComment(Base, BaseModel):
3436 class ChangesetComment(Base, BaseModel):
3428 __tablename__ = 'changeset_comments'
3437 __tablename__ = 'changeset_comments'
3429 __table_args__ = (
3438 __table_args__ = (
3430 Index('cc_revision_idx', 'revision'),
3439 Index('cc_revision_idx', 'revision'),
3431 base_table_args,
3440 base_table_args,
3432 )
3441 )
3433
3442
3434 COMMENT_OUTDATED = u'comment_outdated'
3443 COMMENT_OUTDATED = u'comment_outdated'
3435 COMMENT_TYPE_NOTE = u'note'
3444 COMMENT_TYPE_NOTE = u'note'
3436 COMMENT_TYPE_TODO = u'todo'
3445 COMMENT_TYPE_TODO = u'todo'
3437 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3446 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3438
3447
3439 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3448 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3440 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3449 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3441 revision = Column('revision', String(40), nullable=True)
3450 revision = Column('revision', String(40), nullable=True)
3442 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3451 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3443 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3452 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3444 line_no = Column('line_no', Unicode(10), nullable=True)
3453 line_no = Column('line_no', Unicode(10), nullable=True)
3445 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3454 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3446 f_path = Column('f_path', Unicode(1000), nullable=True)
3455 f_path = Column('f_path', Unicode(1000), nullable=True)
3447 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3456 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3448 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3457 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3449 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3458 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3450 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3459 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3451 renderer = Column('renderer', Unicode(64), nullable=True)
3460 renderer = Column('renderer', Unicode(64), nullable=True)
3452 display_state = Column('display_state', Unicode(128), nullable=True)
3461 display_state = Column('display_state', Unicode(128), nullable=True)
3453
3462
3454 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3463 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3455 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3464 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3456
3465
3457 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3466 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3458 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3467 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3459
3468
3460 author = relationship('User', lazy='joined')
3469 author = relationship('User', lazy='joined')
3461 repo = relationship('Repository')
3470 repo = relationship('Repository')
3462 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3471 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3463 pull_request = relationship('PullRequest', lazy='joined')
3472 pull_request = relationship('PullRequest', lazy='joined')
3464 pull_request_version = relationship('PullRequestVersion')
3473 pull_request_version = relationship('PullRequestVersion')
3465
3474
3466 @classmethod
3475 @classmethod
3467 def get_users(cls, revision=None, pull_request_id=None):
3476 def get_users(cls, revision=None, pull_request_id=None):
3468 """
3477 """
3469 Returns user associated with this ChangesetComment. ie those
3478 Returns user associated with this ChangesetComment. ie those
3470 who actually commented
3479 who actually commented
3471
3480
3472 :param cls:
3481 :param cls:
3473 :param revision:
3482 :param revision:
3474 """
3483 """
3475 q = Session().query(User)\
3484 q = Session().query(User)\
3476 .join(ChangesetComment.author)
3485 .join(ChangesetComment.author)
3477 if revision:
3486 if revision:
3478 q = q.filter(cls.revision == revision)
3487 q = q.filter(cls.revision == revision)
3479 elif pull_request_id:
3488 elif pull_request_id:
3480 q = q.filter(cls.pull_request_id == pull_request_id)
3489 q = q.filter(cls.pull_request_id == pull_request_id)
3481 return q.all()
3490 return q.all()
3482
3491
3483 @classmethod
3492 @classmethod
3484 def get_index_from_version(cls, pr_version, versions):
3493 def get_index_from_version(cls, pr_version, versions):
3485 num_versions = [x.pull_request_version_id for x in versions]
3494 num_versions = [x.pull_request_version_id for x in versions]
3486 try:
3495 try:
3487 return num_versions.index(pr_version) +1
3496 return num_versions.index(pr_version) +1
3488 except (IndexError, ValueError):
3497 except (IndexError, ValueError):
3489 return
3498 return
3490
3499
3491 @property
3500 @property
3492 def outdated(self):
3501 def outdated(self):
3493 return self.display_state == self.COMMENT_OUTDATED
3502 return self.display_state == self.COMMENT_OUTDATED
3494
3503
3495 def outdated_at_version(self, version):
3504 def outdated_at_version(self, version):
3496 """
3505 """
3497 Checks if comment is outdated for given pull request version
3506 Checks if comment is outdated for given pull request version
3498 """
3507 """
3499 return self.outdated and self.pull_request_version_id != version
3508 return self.outdated and self.pull_request_version_id != version
3500
3509
3501 def older_than_version(self, version):
3510 def older_than_version(self, version):
3502 """
3511 """
3503 Checks if comment is made from previous version than given
3512 Checks if comment is made from previous version than given
3504 """
3513 """
3505 if version is None:
3514 if version is None:
3506 return self.pull_request_version_id is not None
3515 return self.pull_request_version_id is not None
3507
3516
3508 return self.pull_request_version_id < version
3517 return self.pull_request_version_id < version
3509
3518
3510 @property
3519 @property
3511 def resolved(self):
3520 def resolved(self):
3512 return self.resolved_by[0] if self.resolved_by else None
3521 return self.resolved_by[0] if self.resolved_by else None
3513
3522
3514 @property
3523 @property
3515 def is_todo(self):
3524 def is_todo(self):
3516 return self.comment_type == self.COMMENT_TYPE_TODO
3525 return self.comment_type == self.COMMENT_TYPE_TODO
3517
3526
3518 @property
3527 @property
3519 def is_inline(self):
3528 def is_inline(self):
3520 return self.line_no and self.f_path
3529 return self.line_no and self.f_path
3521
3530
3522 def get_index_version(self, versions):
3531 def get_index_version(self, versions):
3523 return self.get_index_from_version(
3532 return self.get_index_from_version(
3524 self.pull_request_version_id, versions)
3533 self.pull_request_version_id, versions)
3525
3534
3526 def __repr__(self):
3535 def __repr__(self):
3527 if self.comment_id:
3536 if self.comment_id:
3528 return '<DB:Comment #%s>' % self.comment_id
3537 return '<DB:Comment #%s>' % self.comment_id
3529 else:
3538 else:
3530 return '<DB:Comment at %#x>' % id(self)
3539 return '<DB:Comment at %#x>' % id(self)
3531
3540
3532 def get_api_data(self):
3541 def get_api_data(self):
3533 comment = self
3542 comment = self
3534 data = {
3543 data = {
3535 'comment_id': comment.comment_id,
3544 'comment_id': comment.comment_id,
3536 'comment_type': comment.comment_type,
3545 'comment_type': comment.comment_type,
3537 'comment_text': comment.text,
3546 'comment_text': comment.text,
3538 'comment_status': comment.status_change,
3547 'comment_status': comment.status_change,
3539 'comment_f_path': comment.f_path,
3548 'comment_f_path': comment.f_path,
3540 'comment_lineno': comment.line_no,
3549 'comment_lineno': comment.line_no,
3541 'comment_author': comment.author,
3550 'comment_author': comment.author,
3542 'comment_created_on': comment.created_on,
3551 'comment_created_on': comment.created_on,
3543 'comment_resolved_by': self.resolved
3552 'comment_resolved_by': self.resolved
3544 }
3553 }
3545 return data
3554 return data
3546
3555
3547 def __json__(self):
3556 def __json__(self):
3548 data = dict()
3557 data = dict()
3549 data.update(self.get_api_data())
3558 data.update(self.get_api_data())
3550 return data
3559 return data
3551
3560
3552
3561
3553 class ChangesetStatus(Base, BaseModel):
3562 class ChangesetStatus(Base, BaseModel):
3554 __tablename__ = 'changeset_statuses'
3563 __tablename__ = 'changeset_statuses'
3555 __table_args__ = (
3564 __table_args__ = (
3556 Index('cs_revision_idx', 'revision'),
3565 Index('cs_revision_idx', 'revision'),
3557 Index('cs_version_idx', 'version'),
3566 Index('cs_version_idx', 'version'),
3558 UniqueConstraint('repo_id', 'revision', 'version'),
3567 UniqueConstraint('repo_id', 'revision', 'version'),
3559 base_table_args
3568 base_table_args
3560 )
3569 )
3561
3570
3562 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3571 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3563 STATUS_APPROVED = 'approved'
3572 STATUS_APPROVED = 'approved'
3564 STATUS_REJECTED = 'rejected'
3573 STATUS_REJECTED = 'rejected'
3565 STATUS_UNDER_REVIEW = 'under_review'
3574 STATUS_UNDER_REVIEW = 'under_review'
3566
3575
3567 STATUSES = [
3576 STATUSES = [
3568 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3577 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3569 (STATUS_APPROVED, _("Approved")),
3578 (STATUS_APPROVED, _("Approved")),
3570 (STATUS_REJECTED, _("Rejected")),
3579 (STATUS_REJECTED, _("Rejected")),
3571 (STATUS_UNDER_REVIEW, _("Under Review")),
3580 (STATUS_UNDER_REVIEW, _("Under Review")),
3572 ]
3581 ]
3573
3582
3574 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3583 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3575 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3584 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3576 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3585 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3577 revision = Column('revision', String(40), nullable=False)
3586 revision = Column('revision', String(40), nullable=False)
3578 status = Column('status', String(128), nullable=False, default=DEFAULT)
3587 status = Column('status', String(128), nullable=False, default=DEFAULT)
3579 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3588 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3580 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3589 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3581 version = Column('version', Integer(), nullable=False, default=0)
3590 version = Column('version', Integer(), nullable=False, default=0)
3582 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3591 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3583
3592
3584 author = relationship('User', lazy='joined')
3593 author = relationship('User', lazy='joined')
3585 repo = relationship('Repository')
3594 repo = relationship('Repository')
3586 comment = relationship('ChangesetComment', lazy='joined')
3595 comment = relationship('ChangesetComment', lazy='joined')
3587 pull_request = relationship('PullRequest', lazy='joined')
3596 pull_request = relationship('PullRequest', lazy='joined')
3588
3597
3589 def __unicode__(self):
3598 def __unicode__(self):
3590 return u"<%s('%s[v%s]:%s')>" % (
3599 return u"<%s('%s[v%s]:%s')>" % (
3591 self.__class__.__name__,
3600 self.__class__.__name__,
3592 self.status, self.version, self.author
3601 self.status, self.version, self.author
3593 )
3602 )
3594
3603
3595 @classmethod
3604 @classmethod
3596 def get_status_lbl(cls, value):
3605 def get_status_lbl(cls, value):
3597 return dict(cls.STATUSES).get(value)
3606 return dict(cls.STATUSES).get(value)
3598
3607
3599 @property
3608 @property
3600 def status_lbl(self):
3609 def status_lbl(self):
3601 return ChangesetStatus.get_status_lbl(self.status)
3610 return ChangesetStatus.get_status_lbl(self.status)
3602
3611
3603 def get_api_data(self):
3612 def get_api_data(self):
3604 status = self
3613 status = self
3605 data = {
3614 data = {
3606 'status_id': status.changeset_status_id,
3615 'status_id': status.changeset_status_id,
3607 'status': status.status,
3616 'status': status.status,
3608 }
3617 }
3609 return data
3618 return data
3610
3619
3611 def __json__(self):
3620 def __json__(self):
3612 data = dict()
3621 data = dict()
3613 data.update(self.get_api_data())
3622 data.update(self.get_api_data())
3614 return data
3623 return data
3615
3624
3616
3625
3617 class _SetState(object):
3626 class _SetState(object):
3618 """
3627 """
3619 Context processor allowing changing state for sensitive operation such as
3628 Context processor allowing changing state for sensitive operation such as
3620 pull request update or merge
3629 pull request update or merge
3621 """
3630 """
3622
3631
3623 def __init__(self, pull_request, pr_state, back_state=None):
3632 def __init__(self, pull_request, pr_state, back_state=None):
3624 self._pr = pull_request
3633 self._pr = pull_request
3625 self._org_state = back_state or pull_request.pull_request_state
3634 self._org_state = back_state or pull_request.pull_request_state
3626 self._pr_state = pr_state
3635 self._pr_state = pr_state
3627
3636
3628 def __enter__(self):
3637 def __enter__(self):
3629 log.debug('StateLock: entering set state context, setting state to: `%s`',
3638 log.debug('StateLock: entering set state context, setting state to: `%s`',
3630 self._pr_state)
3639 self._pr_state)
3631 self._pr.pull_request_state = self._pr_state
3640 self._pr.pull_request_state = self._pr_state
3632 Session().add(self._pr)
3641 Session().add(self._pr)
3633 Session().commit()
3642 Session().commit()
3634
3643
3635 def __exit__(self, exc_type, exc_val, exc_tb):
3644 def __exit__(self, exc_type, exc_val, exc_tb):
3636 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3645 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3637 self._org_state)
3646 self._org_state)
3638 self._pr.pull_request_state = self._org_state
3647 self._pr.pull_request_state = self._org_state
3639 Session().add(self._pr)
3648 Session().add(self._pr)
3640 Session().commit()
3649 Session().commit()
3641
3650
3642
3651
3643 class _PullRequestBase(BaseModel):
3652 class _PullRequestBase(BaseModel):
3644 """
3653 """
3645 Common attributes of pull request and version entries.
3654 Common attributes of pull request and version entries.
3646 """
3655 """
3647
3656
3648 # .status values
3657 # .status values
3649 STATUS_NEW = u'new'
3658 STATUS_NEW = u'new'
3650 STATUS_OPEN = u'open'
3659 STATUS_OPEN = u'open'
3651 STATUS_CLOSED = u'closed'
3660 STATUS_CLOSED = u'closed'
3652
3661
3653 # available states
3662 # available states
3654 STATE_CREATING = u'creating'
3663 STATE_CREATING = u'creating'
3655 STATE_UPDATING = u'updating'
3664 STATE_UPDATING = u'updating'
3656 STATE_MERGING = u'merging'
3665 STATE_MERGING = u'merging'
3657 STATE_CREATED = u'created'
3666 STATE_CREATED = u'created'
3658
3667
3659 title = Column('title', Unicode(255), nullable=True)
3668 title = Column('title', Unicode(255), nullable=True)
3660 description = Column(
3669 description = Column(
3661 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3670 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3662 nullable=True)
3671 nullable=True)
3663 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3672 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3664
3673
3665 # new/open/closed status of pull request (not approve/reject/etc)
3674 # new/open/closed status of pull request (not approve/reject/etc)
3666 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3675 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3667 created_on = Column(
3676 created_on = Column(
3668 'created_on', DateTime(timezone=False), nullable=False,
3677 'created_on', DateTime(timezone=False), nullable=False,
3669 default=datetime.datetime.now)
3678 default=datetime.datetime.now)
3670 updated_on = Column(
3679 updated_on = Column(
3671 'updated_on', DateTime(timezone=False), nullable=False,
3680 'updated_on', DateTime(timezone=False), nullable=False,
3672 default=datetime.datetime.now)
3681 default=datetime.datetime.now)
3673
3682
3674 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3683 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3675
3684
3676 @declared_attr
3685 @declared_attr
3677 def user_id(cls):
3686 def user_id(cls):
3678 return Column(
3687 return Column(
3679 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3688 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3680 unique=None)
3689 unique=None)
3681
3690
3682 # 500 revisions max
3691 # 500 revisions max
3683 _revisions = Column(
3692 _revisions = Column(
3684 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3693 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3685
3694
3686 @declared_attr
3695 @declared_attr
3687 def source_repo_id(cls):
3696 def source_repo_id(cls):
3688 # TODO: dan: rename column to source_repo_id
3697 # TODO: dan: rename column to source_repo_id
3689 return Column(
3698 return Column(
3690 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3699 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3691 nullable=False)
3700 nullable=False)
3692
3701
3693 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3702 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3694
3703
3695 @hybrid_property
3704 @hybrid_property
3696 def source_ref(self):
3705 def source_ref(self):
3697 return self._source_ref
3706 return self._source_ref
3698
3707
3699 @source_ref.setter
3708 @source_ref.setter
3700 def source_ref(self, val):
3709 def source_ref(self, val):
3701 parts = (val or '').split(':')
3710 parts = (val or '').split(':')
3702 if len(parts) != 3:
3711 if len(parts) != 3:
3703 raise ValueError(
3712 raise ValueError(
3704 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3713 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3705 self._source_ref = safe_unicode(val)
3714 self._source_ref = safe_unicode(val)
3706
3715
3707 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3716 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3708
3717
3709 @hybrid_property
3718 @hybrid_property
3710 def target_ref(self):
3719 def target_ref(self):
3711 return self._target_ref
3720 return self._target_ref
3712
3721
3713 @target_ref.setter
3722 @target_ref.setter
3714 def target_ref(self, val):
3723 def target_ref(self, val):
3715 parts = (val or '').split(':')
3724 parts = (val or '').split(':')
3716 if len(parts) != 3:
3725 if len(parts) != 3:
3717 raise ValueError(
3726 raise ValueError(
3718 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3727 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3719 self._target_ref = safe_unicode(val)
3728 self._target_ref = safe_unicode(val)
3720
3729
3721 @declared_attr
3730 @declared_attr
3722 def target_repo_id(cls):
3731 def target_repo_id(cls):
3723 # TODO: dan: rename column to target_repo_id
3732 # TODO: dan: rename column to target_repo_id
3724 return Column(
3733 return Column(
3725 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3734 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3726 nullable=False)
3735 nullable=False)
3727
3736
3728 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3737 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3729
3738
3730 # TODO: dan: rename column to last_merge_source_rev
3739 # TODO: dan: rename column to last_merge_source_rev
3731 _last_merge_source_rev = Column(
3740 _last_merge_source_rev = Column(
3732 'last_merge_org_rev', String(40), nullable=True)
3741 'last_merge_org_rev', String(40), nullable=True)
3733 # TODO: dan: rename column to last_merge_target_rev
3742 # TODO: dan: rename column to last_merge_target_rev
3734 _last_merge_target_rev = Column(
3743 _last_merge_target_rev = Column(
3735 'last_merge_other_rev', String(40), nullable=True)
3744 'last_merge_other_rev', String(40), nullable=True)
3736 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3745 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3737 merge_rev = Column('merge_rev', String(40), nullable=True)
3746 merge_rev = Column('merge_rev', String(40), nullable=True)
3738
3747
3739 reviewer_data = Column(
3748 reviewer_data = Column(
3740 'reviewer_data_json', MutationObj.as_mutable(
3749 'reviewer_data_json', MutationObj.as_mutable(
3741 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3750 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3742
3751
3743 @property
3752 @property
3744 def reviewer_data_json(self):
3753 def reviewer_data_json(self):
3745 return json.dumps(self.reviewer_data)
3754 return json.dumps(self.reviewer_data)
3746
3755
3747 @hybrid_property
3756 @hybrid_property
3748 def description_safe(self):
3757 def description_safe(self):
3749 from rhodecode.lib import helpers as h
3758 from rhodecode.lib import helpers as h
3750 return h.escape(self.description)
3759 return h.escape(self.description)
3751
3760
3752 @hybrid_property
3761 @hybrid_property
3753 def revisions(self):
3762 def revisions(self):
3754 return self._revisions.split(':') if self._revisions else []
3763 return self._revisions.split(':') if self._revisions else []
3755
3764
3756 @revisions.setter
3765 @revisions.setter
3757 def revisions(self, val):
3766 def revisions(self, val):
3758 self._revisions = ':'.join(val)
3767 self._revisions = ':'.join(val)
3759
3768
3760 @hybrid_property
3769 @hybrid_property
3761 def last_merge_status(self):
3770 def last_merge_status(self):
3762 return safe_int(self._last_merge_status)
3771 return safe_int(self._last_merge_status)
3763
3772
3764 @last_merge_status.setter
3773 @last_merge_status.setter
3765 def last_merge_status(self, val):
3774 def last_merge_status(self, val):
3766 self._last_merge_status = val
3775 self._last_merge_status = val
3767
3776
3768 @declared_attr
3777 @declared_attr
3769 def author(cls):
3778 def author(cls):
3770 return relationship('User', lazy='joined')
3779 return relationship('User', lazy='joined')
3771
3780
3772 @declared_attr
3781 @declared_attr
3773 def source_repo(cls):
3782 def source_repo(cls):
3774 return relationship(
3783 return relationship(
3775 'Repository',
3784 'Repository',
3776 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3785 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3777
3786
3778 @property
3787 @property
3779 def source_ref_parts(self):
3788 def source_ref_parts(self):
3780 return self.unicode_to_reference(self.source_ref)
3789 return self.unicode_to_reference(self.source_ref)
3781
3790
3782 @declared_attr
3791 @declared_attr
3783 def target_repo(cls):
3792 def target_repo(cls):
3784 return relationship(
3793 return relationship(
3785 'Repository',
3794 'Repository',
3786 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3795 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3787
3796
3788 @property
3797 @property
3789 def target_ref_parts(self):
3798 def target_ref_parts(self):
3790 return self.unicode_to_reference(self.target_ref)
3799 return self.unicode_to_reference(self.target_ref)
3791
3800
3792 @property
3801 @property
3793 def shadow_merge_ref(self):
3802 def shadow_merge_ref(self):
3794 return self.unicode_to_reference(self._shadow_merge_ref)
3803 return self.unicode_to_reference(self._shadow_merge_ref)
3795
3804
3796 @shadow_merge_ref.setter
3805 @shadow_merge_ref.setter
3797 def shadow_merge_ref(self, ref):
3806 def shadow_merge_ref(self, ref):
3798 self._shadow_merge_ref = self.reference_to_unicode(ref)
3807 self._shadow_merge_ref = self.reference_to_unicode(ref)
3799
3808
3800 @staticmethod
3809 @staticmethod
3801 def unicode_to_reference(raw):
3810 def unicode_to_reference(raw):
3802 """
3811 """
3803 Convert a unicode (or string) to a reference object.
3812 Convert a unicode (or string) to a reference object.
3804 If unicode evaluates to False it returns None.
3813 If unicode evaluates to False it returns None.
3805 """
3814 """
3806 if raw:
3815 if raw:
3807 refs = raw.split(':')
3816 refs = raw.split(':')
3808 return Reference(*refs)
3817 return Reference(*refs)
3809 else:
3818 else:
3810 return None
3819 return None
3811
3820
3812 @staticmethod
3821 @staticmethod
3813 def reference_to_unicode(ref):
3822 def reference_to_unicode(ref):
3814 """
3823 """
3815 Convert a reference object to unicode.
3824 Convert a reference object to unicode.
3816 If reference is None it returns None.
3825 If reference is None it returns None.
3817 """
3826 """
3818 if ref:
3827 if ref:
3819 return u':'.join(ref)
3828 return u':'.join(ref)
3820 else:
3829 else:
3821 return None
3830 return None
3822
3831
3823 def get_api_data(self, with_merge_state=True):
3832 def get_api_data(self, with_merge_state=True):
3824 from rhodecode.model.pull_request import PullRequestModel
3833 from rhodecode.model.pull_request import PullRequestModel
3825
3834
3826 pull_request = self
3835 pull_request = self
3827 if with_merge_state:
3836 if with_merge_state:
3828 merge_status = PullRequestModel().merge_status(pull_request)
3837 merge_status = PullRequestModel().merge_status(pull_request)
3829 merge_state = {
3838 merge_state = {
3830 'status': merge_status[0],
3839 'status': merge_status[0],
3831 'message': safe_unicode(merge_status[1]),
3840 'message': safe_unicode(merge_status[1]),
3832 }
3841 }
3833 else:
3842 else:
3834 merge_state = {'status': 'not_available',
3843 merge_state = {'status': 'not_available',
3835 'message': 'not_available'}
3844 'message': 'not_available'}
3836
3845
3837 merge_data = {
3846 merge_data = {
3838 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3847 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3839 'reference': (
3848 'reference': (
3840 pull_request.shadow_merge_ref._asdict()
3849 pull_request.shadow_merge_ref._asdict()
3841 if pull_request.shadow_merge_ref else None),
3850 if pull_request.shadow_merge_ref else None),
3842 }
3851 }
3843
3852
3844 data = {
3853 data = {
3845 'pull_request_id': pull_request.pull_request_id,
3854 'pull_request_id': pull_request.pull_request_id,
3846 'url': PullRequestModel().get_url(pull_request),
3855 'url': PullRequestModel().get_url(pull_request),
3847 'title': pull_request.title,
3856 'title': pull_request.title,
3848 'description': pull_request.description,
3857 'description': pull_request.description,
3849 'status': pull_request.status,
3858 'status': pull_request.status,
3850 'state': pull_request.pull_request_state,
3859 'state': pull_request.pull_request_state,
3851 'created_on': pull_request.created_on,
3860 'created_on': pull_request.created_on,
3852 'updated_on': pull_request.updated_on,
3861 'updated_on': pull_request.updated_on,
3853 'commit_ids': pull_request.revisions,
3862 'commit_ids': pull_request.revisions,
3854 'review_status': pull_request.calculated_review_status(),
3863 'review_status': pull_request.calculated_review_status(),
3855 'mergeable': merge_state,
3864 'mergeable': merge_state,
3856 'source': {
3865 'source': {
3857 'clone_url': pull_request.source_repo.clone_url(),
3866 'clone_url': pull_request.source_repo.clone_url(),
3858 'repository': pull_request.source_repo.repo_name,
3867 'repository': pull_request.source_repo.repo_name,
3859 'reference': {
3868 'reference': {
3860 'name': pull_request.source_ref_parts.name,
3869 'name': pull_request.source_ref_parts.name,
3861 'type': pull_request.source_ref_parts.type,
3870 'type': pull_request.source_ref_parts.type,
3862 'commit_id': pull_request.source_ref_parts.commit_id,
3871 'commit_id': pull_request.source_ref_parts.commit_id,
3863 },
3872 },
3864 },
3873 },
3865 'target': {
3874 'target': {
3866 'clone_url': pull_request.target_repo.clone_url(),
3875 'clone_url': pull_request.target_repo.clone_url(),
3867 'repository': pull_request.target_repo.repo_name,
3876 'repository': pull_request.target_repo.repo_name,
3868 'reference': {
3877 'reference': {
3869 'name': pull_request.target_ref_parts.name,
3878 'name': pull_request.target_ref_parts.name,
3870 'type': pull_request.target_ref_parts.type,
3879 'type': pull_request.target_ref_parts.type,
3871 'commit_id': pull_request.target_ref_parts.commit_id,
3880 'commit_id': pull_request.target_ref_parts.commit_id,
3872 },
3881 },
3873 },
3882 },
3874 'merge': merge_data,
3883 'merge': merge_data,
3875 'author': pull_request.author.get_api_data(include_secrets=False,
3884 'author': pull_request.author.get_api_data(include_secrets=False,
3876 details='basic'),
3885 details='basic'),
3877 'reviewers': [
3886 'reviewers': [
3878 {
3887 {
3879 'user': reviewer.get_api_data(include_secrets=False,
3888 'user': reviewer.get_api_data(include_secrets=False,
3880 details='basic'),
3889 details='basic'),
3881 'reasons': reasons,
3890 'reasons': reasons,
3882 'review_status': st[0][1].status if st else 'not_reviewed',
3891 'review_status': st[0][1].status if st else 'not_reviewed',
3883 }
3892 }
3884 for obj, reviewer, reasons, mandatory, st in
3893 for obj, reviewer, reasons, mandatory, st in
3885 pull_request.reviewers_statuses()
3894 pull_request.reviewers_statuses()
3886 ]
3895 ]
3887 }
3896 }
3888
3897
3889 return data
3898 return data
3890
3899
3891 def set_state(self, pull_request_state, final_state=None):
3900 def set_state(self, pull_request_state, final_state=None):
3892 """
3901 """
3893 # goes from initial state to updating to initial state.
3902 # goes from initial state to updating to initial state.
3894 # initial state can be changed by specifying back_state=
3903 # initial state can be changed by specifying back_state=
3895 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
3904 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
3896 pull_request.merge()
3905 pull_request.merge()
3897
3906
3898 :param pull_request_state:
3907 :param pull_request_state:
3899 :param final_state:
3908 :param final_state:
3900
3909
3901 """
3910 """
3902
3911
3903 return _SetState(self, pull_request_state, back_state=final_state)
3912 return _SetState(self, pull_request_state, back_state=final_state)
3904
3913
3905
3914
3906 class PullRequest(Base, _PullRequestBase):
3915 class PullRequest(Base, _PullRequestBase):
3907 __tablename__ = 'pull_requests'
3916 __tablename__ = 'pull_requests'
3908 __table_args__ = (
3917 __table_args__ = (
3909 base_table_args,
3918 base_table_args,
3910 )
3919 )
3911
3920
3912 pull_request_id = Column(
3921 pull_request_id = Column(
3913 'pull_request_id', Integer(), nullable=False, primary_key=True)
3922 'pull_request_id', Integer(), nullable=False, primary_key=True)
3914
3923
3915 def __repr__(self):
3924 def __repr__(self):
3916 if self.pull_request_id:
3925 if self.pull_request_id:
3917 return '<DB:PullRequest #%s>' % self.pull_request_id
3926 return '<DB:PullRequest #%s>' % self.pull_request_id
3918 else:
3927 else:
3919 return '<DB:PullRequest at %#x>' % id(self)
3928 return '<DB:PullRequest at %#x>' % id(self)
3920
3929
3921 reviewers = relationship('PullRequestReviewers',
3930 reviewers = relationship('PullRequestReviewers',
3922 cascade="all, delete, delete-orphan")
3931 cascade="all, delete, delete-orphan")
3923 statuses = relationship('ChangesetStatus',
3932 statuses = relationship('ChangesetStatus',
3924 cascade="all, delete, delete-orphan")
3933 cascade="all, delete, delete-orphan")
3925 comments = relationship('ChangesetComment',
3934 comments = relationship('ChangesetComment',
3926 cascade="all, delete, delete-orphan")
3935 cascade="all, delete, delete-orphan")
3927 versions = relationship('PullRequestVersion',
3936 versions = relationship('PullRequestVersion',
3928 cascade="all, delete, delete-orphan",
3937 cascade="all, delete, delete-orphan",
3929 lazy='dynamic')
3938 lazy='dynamic')
3930
3939
3931 @classmethod
3940 @classmethod
3932 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3941 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3933 internal_methods=None):
3942 internal_methods=None):
3934
3943
3935 class PullRequestDisplay(object):
3944 class PullRequestDisplay(object):
3936 """
3945 """
3937 Special object wrapper for showing PullRequest data via Versions
3946 Special object wrapper for showing PullRequest data via Versions
3938 It mimics PR object as close as possible. This is read only object
3947 It mimics PR object as close as possible. This is read only object
3939 just for display
3948 just for display
3940 """
3949 """
3941
3950
3942 def __init__(self, attrs, internal=None):
3951 def __init__(self, attrs, internal=None):
3943 self.attrs = attrs
3952 self.attrs = attrs
3944 # internal have priority over the given ones via attrs
3953 # internal have priority over the given ones via attrs
3945 self.internal = internal or ['versions']
3954 self.internal = internal or ['versions']
3946
3955
3947 def __getattr__(self, item):
3956 def __getattr__(self, item):
3948 if item in self.internal:
3957 if item in self.internal:
3949 return getattr(self, item)
3958 return getattr(self, item)
3950 try:
3959 try:
3951 return self.attrs[item]
3960 return self.attrs[item]
3952 except KeyError:
3961 except KeyError:
3953 raise AttributeError(
3962 raise AttributeError(
3954 '%s object has no attribute %s' % (self, item))
3963 '%s object has no attribute %s' % (self, item))
3955
3964
3956 def __repr__(self):
3965 def __repr__(self):
3957 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3966 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3958
3967
3959 def versions(self):
3968 def versions(self):
3960 return pull_request_obj.versions.order_by(
3969 return pull_request_obj.versions.order_by(
3961 PullRequestVersion.pull_request_version_id).all()
3970 PullRequestVersion.pull_request_version_id).all()
3962
3971
3963 def is_closed(self):
3972 def is_closed(self):
3964 return pull_request_obj.is_closed()
3973 return pull_request_obj.is_closed()
3965
3974
3966 @property
3975 @property
3967 def pull_request_version_id(self):
3976 def pull_request_version_id(self):
3968 return getattr(pull_request_obj, 'pull_request_version_id', None)
3977 return getattr(pull_request_obj, 'pull_request_version_id', None)
3969
3978
3970 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3979 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3971
3980
3972 attrs.author = StrictAttributeDict(
3981 attrs.author = StrictAttributeDict(
3973 pull_request_obj.author.get_api_data())
3982 pull_request_obj.author.get_api_data())
3974 if pull_request_obj.target_repo:
3983 if pull_request_obj.target_repo:
3975 attrs.target_repo = StrictAttributeDict(
3984 attrs.target_repo = StrictAttributeDict(
3976 pull_request_obj.target_repo.get_api_data())
3985 pull_request_obj.target_repo.get_api_data())
3977 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3986 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3978
3987
3979 if pull_request_obj.source_repo:
3988 if pull_request_obj.source_repo:
3980 attrs.source_repo = StrictAttributeDict(
3989 attrs.source_repo = StrictAttributeDict(
3981 pull_request_obj.source_repo.get_api_data())
3990 pull_request_obj.source_repo.get_api_data())
3982 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3991 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3983
3992
3984 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3993 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3985 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3994 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3986 attrs.revisions = pull_request_obj.revisions
3995 attrs.revisions = pull_request_obj.revisions
3987
3996
3988 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3997 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3989 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3998 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3990 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3999 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3991
4000
3992 return PullRequestDisplay(attrs, internal=internal_methods)
4001 return PullRequestDisplay(attrs, internal=internal_methods)
3993
4002
3994 def is_closed(self):
4003 def is_closed(self):
3995 return self.status == self.STATUS_CLOSED
4004 return self.status == self.STATUS_CLOSED
3996
4005
3997 def __json__(self):
4006 def __json__(self):
3998 return {
4007 return {
3999 'revisions': self.revisions,
4008 'revisions': self.revisions,
4000 }
4009 }
4001
4010
4002 def calculated_review_status(self):
4011 def calculated_review_status(self):
4003 from rhodecode.model.changeset_status import ChangesetStatusModel
4012 from rhodecode.model.changeset_status import ChangesetStatusModel
4004 return ChangesetStatusModel().calculated_review_status(self)
4013 return ChangesetStatusModel().calculated_review_status(self)
4005
4014
4006 def reviewers_statuses(self):
4015 def reviewers_statuses(self):
4007 from rhodecode.model.changeset_status import ChangesetStatusModel
4016 from rhodecode.model.changeset_status import ChangesetStatusModel
4008 return ChangesetStatusModel().reviewers_statuses(self)
4017 return ChangesetStatusModel().reviewers_statuses(self)
4009
4018
4010 @property
4019 @property
4011 def workspace_id(self):
4020 def workspace_id(self):
4012 from rhodecode.model.pull_request import PullRequestModel
4021 from rhodecode.model.pull_request import PullRequestModel
4013 return PullRequestModel()._workspace_id(self)
4022 return PullRequestModel()._workspace_id(self)
4014
4023
4015 def get_shadow_repo(self):
4024 def get_shadow_repo(self):
4016 workspace_id = self.workspace_id
4025 workspace_id = self.workspace_id
4017 vcs_obj = self.target_repo.scm_instance()
4026 vcs_obj = self.target_repo.scm_instance()
4018 shadow_repository_path = vcs_obj._get_shadow_repository_path(
4027 shadow_repository_path = vcs_obj._get_shadow_repository_path(
4019 self.target_repo.repo_id, workspace_id)
4028 self.target_repo.repo_id, workspace_id)
4020 if os.path.isdir(shadow_repository_path):
4029 if os.path.isdir(shadow_repository_path):
4021 return vcs_obj._get_shadow_instance(shadow_repository_path)
4030 return vcs_obj._get_shadow_instance(shadow_repository_path)
4022
4031
4023
4032
4024 class PullRequestVersion(Base, _PullRequestBase):
4033 class PullRequestVersion(Base, _PullRequestBase):
4025 __tablename__ = 'pull_request_versions'
4034 __tablename__ = 'pull_request_versions'
4026 __table_args__ = (
4035 __table_args__ = (
4027 base_table_args,
4036 base_table_args,
4028 )
4037 )
4029
4038
4030 pull_request_version_id = Column(
4039 pull_request_version_id = Column(
4031 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4040 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4032 pull_request_id = Column(
4041 pull_request_id = Column(
4033 'pull_request_id', Integer(),
4042 'pull_request_id', Integer(),
4034 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4043 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4035 pull_request = relationship('PullRequest')
4044 pull_request = relationship('PullRequest')
4036
4045
4037 def __repr__(self):
4046 def __repr__(self):
4038 if self.pull_request_version_id:
4047 if self.pull_request_version_id:
4039 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4048 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4040 else:
4049 else:
4041 return '<DB:PullRequestVersion at %#x>' % id(self)
4050 return '<DB:PullRequestVersion at %#x>' % id(self)
4042
4051
4043 @property
4052 @property
4044 def reviewers(self):
4053 def reviewers(self):
4045 return self.pull_request.reviewers
4054 return self.pull_request.reviewers
4046
4055
4047 @property
4056 @property
4048 def versions(self):
4057 def versions(self):
4049 return self.pull_request.versions
4058 return self.pull_request.versions
4050
4059
4051 def is_closed(self):
4060 def is_closed(self):
4052 # calculate from original
4061 # calculate from original
4053 return self.pull_request.status == self.STATUS_CLOSED
4062 return self.pull_request.status == self.STATUS_CLOSED
4054
4063
4055 def calculated_review_status(self):
4064 def calculated_review_status(self):
4056 return self.pull_request.calculated_review_status()
4065 return self.pull_request.calculated_review_status()
4057
4066
4058 def reviewers_statuses(self):
4067 def reviewers_statuses(self):
4059 return self.pull_request.reviewers_statuses()
4068 return self.pull_request.reviewers_statuses()
4060
4069
4061
4070
4062 class PullRequestReviewers(Base, BaseModel):
4071 class PullRequestReviewers(Base, BaseModel):
4063 __tablename__ = 'pull_request_reviewers'
4072 __tablename__ = 'pull_request_reviewers'
4064 __table_args__ = (
4073 __table_args__ = (
4065 base_table_args,
4074 base_table_args,
4066 )
4075 )
4067
4076
4068 @hybrid_property
4077 @hybrid_property
4069 def reasons(self):
4078 def reasons(self):
4070 if not self._reasons:
4079 if not self._reasons:
4071 return []
4080 return []
4072 return self._reasons
4081 return self._reasons
4073
4082
4074 @reasons.setter
4083 @reasons.setter
4075 def reasons(self, val):
4084 def reasons(self, val):
4076 val = val or []
4085 val = val or []
4077 if any(not isinstance(x, compat.string_types) for x in val):
4086 if any(not isinstance(x, compat.string_types) for x in val):
4078 raise Exception('invalid reasons type, must be list of strings')
4087 raise Exception('invalid reasons type, must be list of strings')
4079 self._reasons = val
4088 self._reasons = val
4080
4089
4081 pull_requests_reviewers_id = Column(
4090 pull_requests_reviewers_id = Column(
4082 'pull_requests_reviewers_id', Integer(), nullable=False,
4091 'pull_requests_reviewers_id', Integer(), nullable=False,
4083 primary_key=True)
4092 primary_key=True)
4084 pull_request_id = Column(
4093 pull_request_id = Column(
4085 "pull_request_id", Integer(),
4094 "pull_request_id", Integer(),
4086 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4095 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4087 user_id = Column(
4096 user_id = Column(
4088 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4097 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4089 _reasons = Column(
4098 _reasons = Column(
4090 'reason', MutationList.as_mutable(
4099 'reason', MutationList.as_mutable(
4091 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4100 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4092
4101
4093 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4102 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4094 user = relationship('User')
4103 user = relationship('User')
4095 pull_request = relationship('PullRequest')
4104 pull_request = relationship('PullRequest')
4096
4105
4097 rule_data = Column(
4106 rule_data = Column(
4098 'rule_data_json',
4107 'rule_data_json',
4099 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4108 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4100
4109
4101 def rule_user_group_data(self):
4110 def rule_user_group_data(self):
4102 """
4111 """
4103 Returns the voting user group rule data for this reviewer
4112 Returns the voting user group rule data for this reviewer
4104 """
4113 """
4105
4114
4106 if self.rule_data and 'vote_rule' in self.rule_data:
4115 if self.rule_data and 'vote_rule' in self.rule_data:
4107 user_group_data = {}
4116 user_group_data = {}
4108 if 'rule_user_group_entry_id' in self.rule_data:
4117 if 'rule_user_group_entry_id' in self.rule_data:
4109 # means a group with voting rules !
4118 # means a group with voting rules !
4110 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4119 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4111 user_group_data['name'] = self.rule_data['rule_name']
4120 user_group_data['name'] = self.rule_data['rule_name']
4112 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4121 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4113
4122
4114 return user_group_data
4123 return user_group_data
4115
4124
4116 def __unicode__(self):
4125 def __unicode__(self):
4117 return u"<%s('id:%s')>" % (self.__class__.__name__,
4126 return u"<%s('id:%s')>" % (self.__class__.__name__,
4118 self.pull_requests_reviewers_id)
4127 self.pull_requests_reviewers_id)
4119
4128
4120
4129
4121 class Notification(Base, BaseModel):
4130 class Notification(Base, BaseModel):
4122 __tablename__ = 'notifications'
4131 __tablename__ = 'notifications'
4123 __table_args__ = (
4132 __table_args__ = (
4124 Index('notification_type_idx', 'type'),
4133 Index('notification_type_idx', 'type'),
4125 base_table_args,
4134 base_table_args,
4126 )
4135 )
4127
4136
4128 TYPE_CHANGESET_COMMENT = u'cs_comment'
4137 TYPE_CHANGESET_COMMENT = u'cs_comment'
4129 TYPE_MESSAGE = u'message'
4138 TYPE_MESSAGE = u'message'
4130 TYPE_MENTION = u'mention'
4139 TYPE_MENTION = u'mention'
4131 TYPE_REGISTRATION = u'registration'
4140 TYPE_REGISTRATION = u'registration'
4132 TYPE_PULL_REQUEST = u'pull_request'
4141 TYPE_PULL_REQUEST = u'pull_request'
4133 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4142 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4134
4143
4135 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4144 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4136 subject = Column('subject', Unicode(512), nullable=True)
4145 subject = Column('subject', Unicode(512), nullable=True)
4137 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4146 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4138 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4147 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4139 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4148 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4140 type_ = Column('type', Unicode(255))
4149 type_ = Column('type', Unicode(255))
4141
4150
4142 created_by_user = relationship('User')
4151 created_by_user = relationship('User')
4143 notifications_to_users = relationship('UserNotification', lazy='joined',
4152 notifications_to_users = relationship('UserNotification', lazy='joined',
4144 cascade="all, delete, delete-orphan")
4153 cascade="all, delete, delete-orphan")
4145
4154
4146 @property
4155 @property
4147 def recipients(self):
4156 def recipients(self):
4148 return [x.user for x in UserNotification.query()\
4157 return [x.user for x in UserNotification.query()\
4149 .filter(UserNotification.notification == self)\
4158 .filter(UserNotification.notification == self)\
4150 .order_by(UserNotification.user_id.asc()).all()]
4159 .order_by(UserNotification.user_id.asc()).all()]
4151
4160
4152 @classmethod
4161 @classmethod
4153 def create(cls, created_by, subject, body, recipients, type_=None):
4162 def create(cls, created_by, subject, body, recipients, type_=None):
4154 if type_ is None:
4163 if type_ is None:
4155 type_ = Notification.TYPE_MESSAGE
4164 type_ = Notification.TYPE_MESSAGE
4156
4165
4157 notification = cls()
4166 notification = cls()
4158 notification.created_by_user = created_by
4167 notification.created_by_user = created_by
4159 notification.subject = subject
4168 notification.subject = subject
4160 notification.body = body
4169 notification.body = body
4161 notification.type_ = type_
4170 notification.type_ = type_
4162 notification.created_on = datetime.datetime.now()
4171 notification.created_on = datetime.datetime.now()
4163
4172
4164 # For each recipient link the created notification to his account
4173 # For each recipient link the created notification to his account
4165 for u in recipients:
4174 for u in recipients:
4166 assoc = UserNotification()
4175 assoc = UserNotification()
4167 assoc.user_id = u.user_id
4176 assoc.user_id = u.user_id
4168 assoc.notification = notification
4177 assoc.notification = notification
4169
4178
4170 # if created_by is inside recipients mark his notification
4179 # if created_by is inside recipients mark his notification
4171 # as read
4180 # as read
4172 if u.user_id == created_by.user_id:
4181 if u.user_id == created_by.user_id:
4173 assoc.read = True
4182 assoc.read = True
4174 Session().add(assoc)
4183 Session().add(assoc)
4175
4184
4176 Session().add(notification)
4185 Session().add(notification)
4177
4186
4178 return notification
4187 return notification
4179
4188
4180
4189
4181 class UserNotification(Base, BaseModel):
4190 class UserNotification(Base, BaseModel):
4182 __tablename__ = 'user_to_notification'
4191 __tablename__ = 'user_to_notification'
4183 __table_args__ = (
4192 __table_args__ = (
4184 UniqueConstraint('user_id', 'notification_id'),
4193 UniqueConstraint('user_id', 'notification_id'),
4185 base_table_args
4194 base_table_args
4186 )
4195 )
4187
4196
4188 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4197 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4189 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4198 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4190 read = Column('read', Boolean, default=False)
4199 read = Column('read', Boolean, default=False)
4191 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4200 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4192
4201
4193 user = relationship('User', lazy="joined")
4202 user = relationship('User', lazy="joined")
4194 notification = relationship('Notification', lazy="joined",
4203 notification = relationship('Notification', lazy="joined",
4195 order_by=lambda: Notification.created_on.desc(),)
4204 order_by=lambda: Notification.created_on.desc(),)
4196
4205
4197 def mark_as_read(self):
4206 def mark_as_read(self):
4198 self.read = True
4207 self.read = True
4199 Session().add(self)
4208 Session().add(self)
4200
4209
4201
4210
4202 class Gist(Base, BaseModel):
4211 class Gist(Base, BaseModel):
4203 __tablename__ = 'gists'
4212 __tablename__ = 'gists'
4204 __table_args__ = (
4213 __table_args__ = (
4205 Index('g_gist_access_id_idx', 'gist_access_id'),
4214 Index('g_gist_access_id_idx', 'gist_access_id'),
4206 Index('g_created_on_idx', 'created_on'),
4215 Index('g_created_on_idx', 'created_on'),
4207 base_table_args
4216 base_table_args
4208 )
4217 )
4209
4218
4210 GIST_PUBLIC = u'public'
4219 GIST_PUBLIC = u'public'
4211 GIST_PRIVATE = u'private'
4220 GIST_PRIVATE = u'private'
4212 DEFAULT_FILENAME = u'gistfile1.txt'
4221 DEFAULT_FILENAME = u'gistfile1.txt'
4213
4222
4214 ACL_LEVEL_PUBLIC = u'acl_public'
4223 ACL_LEVEL_PUBLIC = u'acl_public'
4215 ACL_LEVEL_PRIVATE = u'acl_private'
4224 ACL_LEVEL_PRIVATE = u'acl_private'
4216
4225
4217 gist_id = Column('gist_id', Integer(), primary_key=True)
4226 gist_id = Column('gist_id', Integer(), primary_key=True)
4218 gist_access_id = Column('gist_access_id', Unicode(250))
4227 gist_access_id = Column('gist_access_id', Unicode(250))
4219 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4228 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4220 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4229 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4221 gist_expires = Column('gist_expires', Float(53), nullable=False)
4230 gist_expires = Column('gist_expires', Float(53), nullable=False)
4222 gist_type = Column('gist_type', Unicode(128), nullable=False)
4231 gist_type = Column('gist_type', Unicode(128), nullable=False)
4223 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4232 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4224 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4233 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4225 acl_level = Column('acl_level', Unicode(128), nullable=True)
4234 acl_level = Column('acl_level', Unicode(128), nullable=True)
4226
4235
4227 owner = relationship('User')
4236 owner = relationship('User')
4228
4237
4229 def __repr__(self):
4238 def __repr__(self):
4230 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4239 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4231
4240
4232 @hybrid_property
4241 @hybrid_property
4233 def description_safe(self):
4242 def description_safe(self):
4234 from rhodecode.lib import helpers as h
4243 from rhodecode.lib import helpers as h
4235 return h.escape(self.gist_description)
4244 return h.escape(self.gist_description)
4236
4245
4237 @classmethod
4246 @classmethod
4238 def get_or_404(cls, id_):
4247 def get_or_404(cls, id_):
4239 from pyramid.httpexceptions import HTTPNotFound
4248 from pyramid.httpexceptions import HTTPNotFound
4240
4249
4241 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4250 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4242 if not res:
4251 if not res:
4243 raise HTTPNotFound()
4252 raise HTTPNotFound()
4244 return res
4253 return res
4245
4254
4246 @classmethod
4255 @classmethod
4247 def get_by_access_id(cls, gist_access_id):
4256 def get_by_access_id(cls, gist_access_id):
4248 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4257 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4249
4258
4250 def gist_url(self):
4259 def gist_url(self):
4251 from rhodecode.model.gist import GistModel
4260 from rhodecode.model.gist import GistModel
4252 return GistModel().get_url(self)
4261 return GistModel().get_url(self)
4253
4262
4254 @classmethod
4263 @classmethod
4255 def base_path(cls):
4264 def base_path(cls):
4256 """
4265 """
4257 Returns base path when all gists are stored
4266 Returns base path when all gists are stored
4258
4267
4259 :param cls:
4268 :param cls:
4260 """
4269 """
4261 from rhodecode.model.gist import GIST_STORE_LOC
4270 from rhodecode.model.gist import GIST_STORE_LOC
4262 q = Session().query(RhodeCodeUi)\
4271 q = Session().query(RhodeCodeUi)\
4263 .filter(RhodeCodeUi.ui_key == URL_SEP)
4272 .filter(RhodeCodeUi.ui_key == URL_SEP)
4264 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4273 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4265 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4274 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4266
4275
4267 def get_api_data(self):
4276 def get_api_data(self):
4268 """
4277 """
4269 Common function for generating gist related data for API
4278 Common function for generating gist related data for API
4270 """
4279 """
4271 gist = self
4280 gist = self
4272 data = {
4281 data = {
4273 'gist_id': gist.gist_id,
4282 'gist_id': gist.gist_id,
4274 'type': gist.gist_type,
4283 'type': gist.gist_type,
4275 'access_id': gist.gist_access_id,
4284 'access_id': gist.gist_access_id,
4276 'description': gist.gist_description,
4285 'description': gist.gist_description,
4277 'url': gist.gist_url(),
4286 'url': gist.gist_url(),
4278 'expires': gist.gist_expires,
4287 'expires': gist.gist_expires,
4279 'created_on': gist.created_on,
4288 'created_on': gist.created_on,
4280 'modified_at': gist.modified_at,
4289 'modified_at': gist.modified_at,
4281 'content': None,
4290 'content': None,
4282 'acl_level': gist.acl_level,
4291 'acl_level': gist.acl_level,
4283 }
4292 }
4284 return data
4293 return data
4285
4294
4286 def __json__(self):
4295 def __json__(self):
4287 data = dict(
4296 data = dict(
4288 )
4297 )
4289 data.update(self.get_api_data())
4298 data.update(self.get_api_data())
4290 return data
4299 return data
4291 # SCM functions
4300 # SCM functions
4292
4301
4293 def scm_instance(self, **kwargs):
4302 def scm_instance(self, **kwargs):
4294 """
4303 """
4295 Get explicit Mercurial repository used
4304 Get explicit Mercurial repository used
4296 :param kwargs:
4305 :param kwargs:
4297 :return:
4306 :return:
4298 """
4307 """
4299 from rhodecode.model.gist import GistModel
4308 from rhodecode.model.gist import GistModel
4300 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4309 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4301 return get_vcs_instance(
4310 return get_vcs_instance(
4302 repo_path=safe_str(full_repo_path), create=False,
4311 repo_path=safe_str(full_repo_path), create=False,
4303 _vcs_alias=GistModel.vcs_backend)
4312 _vcs_alias=GistModel.vcs_backend)
4304
4313
4305
4314
4306 class ExternalIdentity(Base, BaseModel):
4315 class ExternalIdentity(Base, BaseModel):
4307 __tablename__ = 'external_identities'
4316 __tablename__ = 'external_identities'
4308 __table_args__ = (
4317 __table_args__ = (
4309 Index('local_user_id_idx', 'local_user_id'),
4318 Index('local_user_id_idx', 'local_user_id'),
4310 Index('external_id_idx', 'external_id'),
4319 Index('external_id_idx', 'external_id'),
4311 base_table_args
4320 base_table_args
4312 )
4321 )
4313
4322
4314 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4323 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4315 external_username = Column('external_username', Unicode(1024), default=u'')
4324 external_username = Column('external_username', Unicode(1024), default=u'')
4316 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4325 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4317 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4326 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4318 access_token = Column('access_token', String(1024), default=u'')
4327 access_token = Column('access_token', String(1024), default=u'')
4319 alt_token = Column('alt_token', String(1024), default=u'')
4328 alt_token = Column('alt_token', String(1024), default=u'')
4320 token_secret = Column('token_secret', String(1024), default=u'')
4329 token_secret = Column('token_secret', String(1024), default=u'')
4321
4330
4322 @classmethod
4331 @classmethod
4323 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4332 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4324 """
4333 """
4325 Returns ExternalIdentity instance based on search params
4334 Returns ExternalIdentity instance based on search params
4326
4335
4327 :param external_id:
4336 :param external_id:
4328 :param provider_name:
4337 :param provider_name:
4329 :return: ExternalIdentity
4338 :return: ExternalIdentity
4330 """
4339 """
4331 query = cls.query()
4340 query = cls.query()
4332 query = query.filter(cls.external_id == external_id)
4341 query = query.filter(cls.external_id == external_id)
4333 query = query.filter(cls.provider_name == provider_name)
4342 query = query.filter(cls.provider_name == provider_name)
4334 if local_user_id:
4343 if local_user_id:
4335 query = query.filter(cls.local_user_id == local_user_id)
4344 query = query.filter(cls.local_user_id == local_user_id)
4336 return query.first()
4345 return query.first()
4337
4346
4338 @classmethod
4347 @classmethod
4339 def user_by_external_id_and_provider(cls, external_id, provider_name):
4348 def user_by_external_id_and_provider(cls, external_id, provider_name):
4340 """
4349 """
4341 Returns User instance based on search params
4350 Returns User instance based on search params
4342
4351
4343 :param external_id:
4352 :param external_id:
4344 :param provider_name:
4353 :param provider_name:
4345 :return: User
4354 :return: User
4346 """
4355 """
4347 query = User.query()
4356 query = User.query()
4348 query = query.filter(cls.external_id == external_id)
4357 query = query.filter(cls.external_id == external_id)
4349 query = query.filter(cls.provider_name == provider_name)
4358 query = query.filter(cls.provider_name == provider_name)
4350 query = query.filter(User.user_id == cls.local_user_id)
4359 query = query.filter(User.user_id == cls.local_user_id)
4351 return query.first()
4360 return query.first()
4352
4361
4353 @classmethod
4362 @classmethod
4354 def by_local_user_id(cls, local_user_id):
4363 def by_local_user_id(cls, local_user_id):
4355 """
4364 """
4356 Returns all tokens for user
4365 Returns all tokens for user
4357
4366
4358 :param local_user_id:
4367 :param local_user_id:
4359 :return: ExternalIdentity
4368 :return: ExternalIdentity
4360 """
4369 """
4361 query = cls.query()
4370 query = cls.query()
4362 query = query.filter(cls.local_user_id == local_user_id)
4371 query = query.filter(cls.local_user_id == local_user_id)
4363 return query
4372 return query
4364
4373
4365 @classmethod
4374 @classmethod
4366 def load_provider_plugin(cls, plugin_id):
4375 def load_provider_plugin(cls, plugin_id):
4367 from rhodecode.authentication.base import loadplugin
4376 from rhodecode.authentication.base import loadplugin
4368 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4377 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4369 auth_plugin = loadplugin(_plugin_id)
4378 auth_plugin = loadplugin(_plugin_id)
4370 return auth_plugin
4379 return auth_plugin
4371
4380
4372
4381
4373 class Integration(Base, BaseModel):
4382 class Integration(Base, BaseModel):
4374 __tablename__ = 'integrations'
4383 __tablename__ = 'integrations'
4375 __table_args__ = (
4384 __table_args__ = (
4376 base_table_args
4385 base_table_args
4377 )
4386 )
4378
4387
4379 integration_id = Column('integration_id', Integer(), primary_key=True)
4388 integration_id = Column('integration_id', Integer(), primary_key=True)
4380 integration_type = Column('integration_type', String(255))
4389 integration_type = Column('integration_type', String(255))
4381 enabled = Column('enabled', Boolean(), nullable=False)
4390 enabled = Column('enabled', Boolean(), nullable=False)
4382 name = Column('name', String(255), nullable=False)
4391 name = Column('name', String(255), nullable=False)
4383 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4392 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4384 default=False)
4393 default=False)
4385
4394
4386 settings = Column(
4395 settings = Column(
4387 'settings_json', MutationObj.as_mutable(
4396 'settings_json', MutationObj.as_mutable(
4388 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4397 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4389 repo_id = Column(
4398 repo_id = Column(
4390 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4399 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4391 nullable=True, unique=None, default=None)
4400 nullable=True, unique=None, default=None)
4392 repo = relationship('Repository', lazy='joined')
4401 repo = relationship('Repository', lazy='joined')
4393
4402
4394 repo_group_id = Column(
4403 repo_group_id = Column(
4395 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4404 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4396 nullable=True, unique=None, default=None)
4405 nullable=True, unique=None, default=None)
4397 repo_group = relationship('RepoGroup', lazy='joined')
4406 repo_group = relationship('RepoGroup', lazy='joined')
4398
4407
4399 @property
4408 @property
4400 def scope(self):
4409 def scope(self):
4401 if self.repo:
4410 if self.repo:
4402 return repr(self.repo)
4411 return repr(self.repo)
4403 if self.repo_group:
4412 if self.repo_group:
4404 if self.child_repos_only:
4413 if self.child_repos_only:
4405 return repr(self.repo_group) + ' (child repos only)'
4414 return repr(self.repo_group) + ' (child repos only)'
4406 else:
4415 else:
4407 return repr(self.repo_group) + ' (recursive)'
4416 return repr(self.repo_group) + ' (recursive)'
4408 if self.child_repos_only:
4417 if self.child_repos_only:
4409 return 'root_repos'
4418 return 'root_repos'
4410 return 'global'
4419 return 'global'
4411
4420
4412 def __repr__(self):
4421 def __repr__(self):
4413 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4422 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4414
4423
4415
4424
4416 class RepoReviewRuleUser(Base, BaseModel):
4425 class RepoReviewRuleUser(Base, BaseModel):
4417 __tablename__ = 'repo_review_rules_users'
4426 __tablename__ = 'repo_review_rules_users'
4418 __table_args__ = (
4427 __table_args__ = (
4419 base_table_args
4428 base_table_args
4420 )
4429 )
4421
4430
4422 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4431 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4423 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4432 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4424 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4433 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4425 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4434 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4426 user = relationship('User')
4435 user = relationship('User')
4427
4436
4428 def rule_data(self):
4437 def rule_data(self):
4429 return {
4438 return {
4430 'mandatory': self.mandatory
4439 'mandatory': self.mandatory
4431 }
4440 }
4432
4441
4433
4442
4434 class RepoReviewRuleUserGroup(Base, BaseModel):
4443 class RepoReviewRuleUserGroup(Base, BaseModel):
4435 __tablename__ = 'repo_review_rules_users_groups'
4444 __tablename__ = 'repo_review_rules_users_groups'
4436 __table_args__ = (
4445 __table_args__ = (
4437 base_table_args
4446 base_table_args
4438 )
4447 )
4439
4448
4440 VOTE_RULE_ALL = -1
4449 VOTE_RULE_ALL = -1
4441
4450
4442 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4451 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4443 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4452 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4444 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4453 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4445 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4454 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4446 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4455 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4447 users_group = relationship('UserGroup')
4456 users_group = relationship('UserGroup')
4448
4457
4449 def rule_data(self):
4458 def rule_data(self):
4450 return {
4459 return {
4451 'mandatory': self.mandatory,
4460 'mandatory': self.mandatory,
4452 'vote_rule': self.vote_rule
4461 'vote_rule': self.vote_rule
4453 }
4462 }
4454
4463
4455 @property
4464 @property
4456 def vote_rule_label(self):
4465 def vote_rule_label(self):
4457 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4466 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4458 return 'all must vote'
4467 return 'all must vote'
4459 else:
4468 else:
4460 return 'min. vote {}'.format(self.vote_rule)
4469 return 'min. vote {}'.format(self.vote_rule)
4461
4470
4462
4471
4463 class RepoReviewRule(Base, BaseModel):
4472 class RepoReviewRule(Base, BaseModel):
4464 __tablename__ = 'repo_review_rules'
4473 __tablename__ = 'repo_review_rules'
4465 __table_args__ = (
4474 __table_args__ = (
4466 base_table_args
4475 base_table_args
4467 )
4476 )
4468
4477
4469 repo_review_rule_id = Column(
4478 repo_review_rule_id = Column(
4470 'repo_review_rule_id', Integer(), primary_key=True)
4479 'repo_review_rule_id', Integer(), primary_key=True)
4471 repo_id = Column(
4480 repo_id = Column(
4472 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4481 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4473 repo = relationship('Repository', backref='review_rules')
4482 repo = relationship('Repository', backref='review_rules')
4474
4483
4475 review_rule_name = Column('review_rule_name', String(255))
4484 review_rule_name = Column('review_rule_name', String(255))
4476 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4485 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4477 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4486 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4478 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4487 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4479
4488
4480 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4489 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4481 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4490 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4482 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4491 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4483 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4492 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4484
4493
4485 rule_users = relationship('RepoReviewRuleUser')
4494 rule_users = relationship('RepoReviewRuleUser')
4486 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4495 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4487
4496
4488 def _validate_pattern(self, value):
4497 def _validate_pattern(self, value):
4489 re.compile('^' + glob2re(value) + '$')
4498 re.compile('^' + glob2re(value) + '$')
4490
4499
4491 @hybrid_property
4500 @hybrid_property
4492 def source_branch_pattern(self):
4501 def source_branch_pattern(self):
4493 return self._branch_pattern or '*'
4502 return self._branch_pattern or '*'
4494
4503
4495 @source_branch_pattern.setter
4504 @source_branch_pattern.setter
4496 def source_branch_pattern(self, value):
4505 def source_branch_pattern(self, value):
4497 self._validate_pattern(value)
4506 self._validate_pattern(value)
4498 self._branch_pattern = value or '*'
4507 self._branch_pattern = value or '*'
4499
4508
4500 @hybrid_property
4509 @hybrid_property
4501 def target_branch_pattern(self):
4510 def target_branch_pattern(self):
4502 return self._target_branch_pattern or '*'
4511 return self._target_branch_pattern or '*'
4503
4512
4504 @target_branch_pattern.setter
4513 @target_branch_pattern.setter
4505 def target_branch_pattern(self, value):
4514 def target_branch_pattern(self, value):
4506 self._validate_pattern(value)
4515 self._validate_pattern(value)
4507 self._target_branch_pattern = value or '*'
4516 self._target_branch_pattern = value or '*'
4508
4517
4509 @hybrid_property
4518 @hybrid_property
4510 def file_pattern(self):
4519 def file_pattern(self):
4511 return self._file_pattern or '*'
4520 return self._file_pattern or '*'
4512
4521
4513 @file_pattern.setter
4522 @file_pattern.setter
4514 def file_pattern(self, value):
4523 def file_pattern(self, value):
4515 self._validate_pattern(value)
4524 self._validate_pattern(value)
4516 self._file_pattern = value or '*'
4525 self._file_pattern = value or '*'
4517
4526
4518 def matches(self, source_branch, target_branch, files_changed):
4527 def matches(self, source_branch, target_branch, files_changed):
4519 """
4528 """
4520 Check if this review rule matches a branch/files in a pull request
4529 Check if this review rule matches a branch/files in a pull request
4521
4530
4522 :param source_branch: source branch name for the commit
4531 :param source_branch: source branch name for the commit
4523 :param target_branch: target branch name for the commit
4532 :param target_branch: target branch name for the commit
4524 :param files_changed: list of file paths changed in the pull request
4533 :param files_changed: list of file paths changed in the pull request
4525 """
4534 """
4526
4535
4527 source_branch = source_branch or ''
4536 source_branch = source_branch or ''
4528 target_branch = target_branch or ''
4537 target_branch = target_branch or ''
4529 files_changed = files_changed or []
4538 files_changed = files_changed or []
4530
4539
4531 branch_matches = True
4540 branch_matches = True
4532 if source_branch or target_branch:
4541 if source_branch or target_branch:
4533 if self.source_branch_pattern == '*':
4542 if self.source_branch_pattern == '*':
4534 source_branch_match = True
4543 source_branch_match = True
4535 else:
4544 else:
4536 if self.source_branch_pattern.startswith('re:'):
4545 if self.source_branch_pattern.startswith('re:'):
4537 source_pattern = self.source_branch_pattern[3:]
4546 source_pattern = self.source_branch_pattern[3:]
4538 else:
4547 else:
4539 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4548 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4540 source_branch_regex = re.compile(source_pattern)
4549 source_branch_regex = re.compile(source_pattern)
4541 source_branch_match = bool(source_branch_regex.search(source_branch))
4550 source_branch_match = bool(source_branch_regex.search(source_branch))
4542 if self.target_branch_pattern == '*':
4551 if self.target_branch_pattern == '*':
4543 target_branch_match = True
4552 target_branch_match = True
4544 else:
4553 else:
4545 if self.target_branch_pattern.startswith('re:'):
4554 if self.target_branch_pattern.startswith('re:'):
4546 target_pattern = self.target_branch_pattern[3:]
4555 target_pattern = self.target_branch_pattern[3:]
4547 else:
4556 else:
4548 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4557 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4549 target_branch_regex = re.compile(target_pattern)
4558 target_branch_regex = re.compile(target_pattern)
4550 target_branch_match = bool(target_branch_regex.search(target_branch))
4559 target_branch_match = bool(target_branch_regex.search(target_branch))
4551
4560
4552 branch_matches = source_branch_match and target_branch_match
4561 branch_matches = source_branch_match and target_branch_match
4553
4562
4554 files_matches = True
4563 files_matches = True
4555 if self.file_pattern != '*':
4564 if self.file_pattern != '*':
4556 files_matches = False
4565 files_matches = False
4557 if self.file_pattern.startswith('re:'):
4566 if self.file_pattern.startswith('re:'):
4558 file_pattern = self.file_pattern[3:]
4567 file_pattern = self.file_pattern[3:]
4559 else:
4568 else:
4560 file_pattern = glob2re(self.file_pattern)
4569 file_pattern = glob2re(self.file_pattern)
4561 file_regex = re.compile(file_pattern)
4570 file_regex = re.compile(file_pattern)
4562 for filename in files_changed:
4571 for filename in files_changed:
4563 if file_regex.search(filename):
4572 if file_regex.search(filename):
4564 files_matches = True
4573 files_matches = True
4565 break
4574 break
4566
4575
4567 return branch_matches and files_matches
4576 return branch_matches and files_matches
4568
4577
4569 @property
4578 @property
4570 def review_users(self):
4579 def review_users(self):
4571 """ Returns the users which this rule applies to """
4580 """ Returns the users which this rule applies to """
4572
4581
4573 users = collections.OrderedDict()
4582 users = collections.OrderedDict()
4574
4583
4575 for rule_user in self.rule_users:
4584 for rule_user in self.rule_users:
4576 if rule_user.user.active:
4585 if rule_user.user.active:
4577 if rule_user.user not in users:
4586 if rule_user.user not in users:
4578 users[rule_user.user.username] = {
4587 users[rule_user.user.username] = {
4579 'user': rule_user.user,
4588 'user': rule_user.user,
4580 'source': 'user',
4589 'source': 'user',
4581 'source_data': {},
4590 'source_data': {},
4582 'data': rule_user.rule_data()
4591 'data': rule_user.rule_data()
4583 }
4592 }
4584
4593
4585 for rule_user_group in self.rule_user_groups:
4594 for rule_user_group in self.rule_user_groups:
4586 source_data = {
4595 source_data = {
4587 'user_group_id': rule_user_group.users_group.users_group_id,
4596 'user_group_id': rule_user_group.users_group.users_group_id,
4588 'name': rule_user_group.users_group.users_group_name,
4597 'name': rule_user_group.users_group.users_group_name,
4589 'members': len(rule_user_group.users_group.members)
4598 'members': len(rule_user_group.users_group.members)
4590 }
4599 }
4591 for member in rule_user_group.users_group.members:
4600 for member in rule_user_group.users_group.members:
4592 if member.user.active:
4601 if member.user.active:
4593 key = member.user.username
4602 key = member.user.username
4594 if key in users:
4603 if key in users:
4595 # skip this member as we have him already
4604 # skip this member as we have him already
4596 # this prevents from override the "first" matched
4605 # this prevents from override the "first" matched
4597 # users with duplicates in multiple groups
4606 # users with duplicates in multiple groups
4598 continue
4607 continue
4599
4608
4600 users[key] = {
4609 users[key] = {
4601 'user': member.user,
4610 'user': member.user,
4602 'source': 'user_group',
4611 'source': 'user_group',
4603 'source_data': source_data,
4612 'source_data': source_data,
4604 'data': rule_user_group.rule_data()
4613 'data': rule_user_group.rule_data()
4605 }
4614 }
4606
4615
4607 return users
4616 return users
4608
4617
4609 def user_group_vote_rule(self, user_id):
4618 def user_group_vote_rule(self, user_id):
4610
4619
4611 rules = []
4620 rules = []
4612 if not self.rule_user_groups:
4621 if not self.rule_user_groups:
4613 return rules
4622 return rules
4614
4623
4615 for user_group in self.rule_user_groups:
4624 for user_group in self.rule_user_groups:
4616 user_group_members = [x.user_id for x in user_group.users_group.members]
4625 user_group_members = [x.user_id for x in user_group.users_group.members]
4617 if user_id in user_group_members:
4626 if user_id in user_group_members:
4618 rules.append(user_group)
4627 rules.append(user_group)
4619 return rules
4628 return rules
4620
4629
4621 def __repr__(self):
4630 def __repr__(self):
4622 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4631 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4623 self.repo_review_rule_id, self.repo)
4632 self.repo_review_rule_id, self.repo)
4624
4633
4625
4634
4626 class ScheduleEntry(Base, BaseModel):
4635 class ScheduleEntry(Base, BaseModel):
4627 __tablename__ = 'schedule_entries'
4636 __tablename__ = 'schedule_entries'
4628 __table_args__ = (
4637 __table_args__ = (
4629 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4638 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4630 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4639 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4631 base_table_args,
4640 base_table_args,
4632 )
4641 )
4633
4642
4634 schedule_types = ['crontab', 'timedelta', 'integer']
4643 schedule_types = ['crontab', 'timedelta', 'integer']
4635 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4644 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4636
4645
4637 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4646 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4638 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4647 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4639 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4648 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4640
4649
4641 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4650 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4642 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4651 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4643
4652
4644 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4653 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4645 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4654 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4646
4655
4647 # task
4656 # task
4648 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4657 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4649 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4658 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4650 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4659 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4651 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4660 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4652
4661
4653 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4662 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4654 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4663 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4655
4664
4656 @hybrid_property
4665 @hybrid_property
4657 def schedule_type(self):
4666 def schedule_type(self):
4658 return self._schedule_type
4667 return self._schedule_type
4659
4668
4660 @schedule_type.setter
4669 @schedule_type.setter
4661 def schedule_type(self, val):
4670 def schedule_type(self, val):
4662 if val not in self.schedule_types:
4671 if val not in self.schedule_types:
4663 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4672 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4664 val, self.schedule_type))
4673 val, self.schedule_type))
4665
4674
4666 self._schedule_type = val
4675 self._schedule_type = val
4667
4676
4668 @classmethod
4677 @classmethod
4669 def get_uid(cls, obj):
4678 def get_uid(cls, obj):
4670 args = obj.task_args
4679 args = obj.task_args
4671 kwargs = obj.task_kwargs
4680 kwargs = obj.task_kwargs
4672 if isinstance(args, JsonRaw):
4681 if isinstance(args, JsonRaw):
4673 try:
4682 try:
4674 args = json.loads(args)
4683 args = json.loads(args)
4675 except ValueError:
4684 except ValueError:
4676 args = tuple()
4685 args = tuple()
4677
4686
4678 if isinstance(kwargs, JsonRaw):
4687 if isinstance(kwargs, JsonRaw):
4679 try:
4688 try:
4680 kwargs = json.loads(kwargs)
4689 kwargs = json.loads(kwargs)
4681 except ValueError:
4690 except ValueError:
4682 kwargs = dict()
4691 kwargs = dict()
4683
4692
4684 dot_notation = obj.task_dot_notation
4693 dot_notation = obj.task_dot_notation
4685 val = '.'.join(map(safe_str, [
4694 val = '.'.join(map(safe_str, [
4686 sorted(dot_notation), args, sorted(kwargs.items())]))
4695 sorted(dot_notation), args, sorted(kwargs.items())]))
4687 return hashlib.sha1(val).hexdigest()
4696 return hashlib.sha1(val).hexdigest()
4688
4697
4689 @classmethod
4698 @classmethod
4690 def get_by_schedule_name(cls, schedule_name):
4699 def get_by_schedule_name(cls, schedule_name):
4691 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4700 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4692
4701
4693 @classmethod
4702 @classmethod
4694 def get_by_schedule_id(cls, schedule_id):
4703 def get_by_schedule_id(cls, schedule_id):
4695 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4704 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4696
4705
4697 @property
4706 @property
4698 def task(self):
4707 def task(self):
4699 return self.task_dot_notation
4708 return self.task_dot_notation
4700
4709
4701 @property
4710 @property
4702 def schedule(self):
4711 def schedule(self):
4703 from rhodecode.lib.celerylib.utils import raw_2_schedule
4712 from rhodecode.lib.celerylib.utils import raw_2_schedule
4704 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4713 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4705 return schedule
4714 return schedule
4706
4715
4707 @property
4716 @property
4708 def args(self):
4717 def args(self):
4709 try:
4718 try:
4710 return list(self.task_args or [])
4719 return list(self.task_args or [])
4711 except ValueError:
4720 except ValueError:
4712 return list()
4721 return list()
4713
4722
4714 @property
4723 @property
4715 def kwargs(self):
4724 def kwargs(self):
4716 try:
4725 try:
4717 return dict(self.task_kwargs or {})
4726 return dict(self.task_kwargs or {})
4718 except ValueError:
4727 except ValueError:
4719 return dict()
4728 return dict()
4720
4729
4721 def _as_raw(self, val):
4730 def _as_raw(self, val):
4722 if hasattr(val, 'de_coerce'):
4731 if hasattr(val, 'de_coerce'):
4723 val = val.de_coerce()
4732 val = val.de_coerce()
4724 if val:
4733 if val:
4725 val = json.dumps(val)
4734 val = json.dumps(val)
4726
4735
4727 return val
4736 return val
4728
4737
4729 @property
4738 @property
4730 def schedule_definition_raw(self):
4739 def schedule_definition_raw(self):
4731 return self._as_raw(self.schedule_definition)
4740 return self._as_raw(self.schedule_definition)
4732
4741
4733 @property
4742 @property
4734 def args_raw(self):
4743 def args_raw(self):
4735 return self._as_raw(self.task_args)
4744 return self._as_raw(self.task_args)
4736
4745
4737 @property
4746 @property
4738 def kwargs_raw(self):
4747 def kwargs_raw(self):
4739 return self._as_raw(self.task_kwargs)
4748 return self._as_raw(self.task_kwargs)
4740
4749
4741 def __repr__(self):
4750 def __repr__(self):
4742 return '<DB:ScheduleEntry({}:{})>'.format(
4751 return '<DB:ScheduleEntry({}:{})>'.format(
4743 self.schedule_entry_id, self.schedule_name)
4752 self.schedule_entry_id, self.schedule_name)
4744
4753
4745
4754
4746 @event.listens_for(ScheduleEntry, 'before_update')
4755 @event.listens_for(ScheduleEntry, 'before_update')
4747 def update_task_uid(mapper, connection, target):
4756 def update_task_uid(mapper, connection, target):
4748 target.task_uid = ScheduleEntry.get_uid(target)
4757 target.task_uid = ScheduleEntry.get_uid(target)
4749
4758
4750
4759
4751 @event.listens_for(ScheduleEntry, 'before_insert')
4760 @event.listens_for(ScheduleEntry, 'before_insert')
4752 def set_task_uid(mapper, connection, target):
4761 def set_task_uid(mapper, connection, target):
4753 target.task_uid = ScheduleEntry.get_uid(target)
4762 target.task_uid = ScheduleEntry.get_uid(target)
4754
4763
4755
4764
4756 class _BaseBranchPerms(BaseModel):
4765 class _BaseBranchPerms(BaseModel):
4757 @classmethod
4766 @classmethod
4758 def compute_hash(cls, value):
4767 def compute_hash(cls, value):
4759 return sha1_safe(value)
4768 return sha1_safe(value)
4760
4769
4761 @hybrid_property
4770 @hybrid_property
4762 def branch_pattern(self):
4771 def branch_pattern(self):
4763 return self._branch_pattern or '*'
4772 return self._branch_pattern or '*'
4764
4773
4765 @hybrid_property
4774 @hybrid_property
4766 def branch_hash(self):
4775 def branch_hash(self):
4767 return self._branch_hash
4776 return self._branch_hash
4768
4777
4769 def _validate_glob(self, value):
4778 def _validate_glob(self, value):
4770 re.compile('^' + glob2re(value) + '$')
4779 re.compile('^' + glob2re(value) + '$')
4771
4780
4772 @branch_pattern.setter
4781 @branch_pattern.setter
4773 def branch_pattern(self, value):
4782 def branch_pattern(self, value):
4774 self._validate_glob(value)
4783 self._validate_glob(value)
4775 self._branch_pattern = value or '*'
4784 self._branch_pattern = value or '*'
4776 # set the Hash when setting the branch pattern
4785 # set the Hash when setting the branch pattern
4777 self._branch_hash = self.compute_hash(self._branch_pattern)
4786 self._branch_hash = self.compute_hash(self._branch_pattern)
4778
4787
4779 def matches(self, branch):
4788 def matches(self, branch):
4780 """
4789 """
4781 Check if this the branch matches entry
4790 Check if this the branch matches entry
4782
4791
4783 :param branch: branch name for the commit
4792 :param branch: branch name for the commit
4784 """
4793 """
4785
4794
4786 branch = branch or ''
4795 branch = branch or ''
4787
4796
4788 branch_matches = True
4797 branch_matches = True
4789 if branch:
4798 if branch:
4790 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4799 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4791 branch_matches = bool(branch_regex.search(branch))
4800 branch_matches = bool(branch_regex.search(branch))
4792
4801
4793 return branch_matches
4802 return branch_matches
4794
4803
4795
4804
4796 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4805 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4797 __tablename__ = 'user_to_repo_branch_permissions'
4806 __tablename__ = 'user_to_repo_branch_permissions'
4798 __table_args__ = (
4807 __table_args__ = (
4799 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4808 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4800 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4809 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4801 )
4810 )
4802
4811
4803 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4812 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4804
4813
4805 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4814 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4806 repo = relationship('Repository', backref='user_branch_perms')
4815 repo = relationship('Repository', backref='user_branch_perms')
4807
4816
4808 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4817 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4809 permission = relationship('Permission')
4818 permission = relationship('Permission')
4810
4819
4811 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4820 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4812 user_repo_to_perm = relationship('UserRepoToPerm')
4821 user_repo_to_perm = relationship('UserRepoToPerm')
4813
4822
4814 rule_order = Column('rule_order', Integer(), nullable=False)
4823 rule_order = Column('rule_order', Integer(), nullable=False)
4815 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4824 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4816 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4825 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4817
4826
4818 def __unicode__(self):
4827 def __unicode__(self):
4819 return u'<UserBranchPermission(%s => %r)>' % (
4828 return u'<UserBranchPermission(%s => %r)>' % (
4820 self.user_repo_to_perm, self.branch_pattern)
4829 self.user_repo_to_perm, self.branch_pattern)
4821
4830
4822
4831
4823 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4832 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4824 __tablename__ = 'user_group_to_repo_branch_permissions'
4833 __tablename__ = 'user_group_to_repo_branch_permissions'
4825 __table_args__ = (
4834 __table_args__ = (
4826 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4835 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4827 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4836 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4828 )
4837 )
4829
4838
4830 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4839 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4831
4840
4832 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4841 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4833 repo = relationship('Repository', backref='user_group_branch_perms')
4842 repo = relationship('Repository', backref='user_group_branch_perms')
4834
4843
4835 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4844 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4836 permission = relationship('Permission')
4845 permission = relationship('Permission')
4837
4846
4838 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
4847 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
4839 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4848 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4840
4849
4841 rule_order = Column('rule_order', Integer(), nullable=False)
4850 rule_order = Column('rule_order', Integer(), nullable=False)
4842 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4851 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4843 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4852 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4844
4853
4845 def __unicode__(self):
4854 def __unicode__(self):
4846 return u'<UserBranchPermission(%s => %r)>' % (
4855 return u'<UserBranchPermission(%s => %r)>' % (
4847 self.user_group_repo_to_perm, self.branch_pattern)
4856 self.user_group_repo_to_perm, self.branch_pattern)
4848
4857
4849
4858
4850 class UserBookmark(Base, BaseModel):
4859 class UserBookmark(Base, BaseModel):
4851 __tablename__ = 'user_bookmarks'
4860 __tablename__ = 'user_bookmarks'
4852 __table_args__ = (
4861 __table_args__ = (
4853 UniqueConstraint('user_id', 'bookmark_repo_id'),
4862 UniqueConstraint('user_id', 'bookmark_repo_id'),
4854 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
4863 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
4855 UniqueConstraint('user_id', 'bookmark_position'),
4864 UniqueConstraint('user_id', 'bookmark_position'),
4856 base_table_args
4865 base_table_args
4857 )
4866 )
4858
4867
4859 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
4868 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
4860 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
4869 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
4861 position = Column("bookmark_position", Integer(), nullable=False)
4870 position = Column("bookmark_position", Integer(), nullable=False)
4862 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
4871 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
4863 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
4872 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
4864 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4873 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4865
4874
4866 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
4875 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
4867 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
4876 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
4868
4877
4869 user = relationship("User")
4878 user = relationship("User")
4870
4879
4871 repository = relationship("Repository")
4880 repository = relationship("Repository")
4872 repository_group = relationship("RepoGroup")
4881 repository_group = relationship("RepoGroup")
4873
4882
4874 @classmethod
4883 @classmethod
4875 def get_by_position_for_user(cls, position, user_id):
4884 def get_by_position_for_user(cls, position, user_id):
4876 return cls.query() \
4885 return cls.query() \
4877 .filter(UserBookmark.user_id == user_id) \
4886 .filter(UserBookmark.user_id == user_id) \
4878 .filter(UserBookmark.position == position).scalar()
4887 .filter(UserBookmark.position == position).scalar()
4879
4888
4880 @classmethod
4889 @classmethod
4881 def get_bookmarks_for_user(cls, user_id):
4890 def get_bookmarks_for_user(cls, user_id):
4882 return cls.query() \
4891 return cls.query() \
4883 .filter(UserBookmark.user_id == user_id) \
4892 .filter(UserBookmark.user_id == user_id) \
4884 .options(joinedload(UserBookmark.repository)) \
4893 .options(joinedload(UserBookmark.repository)) \
4885 .options(joinedload(UserBookmark.repository_group)) \
4894 .options(joinedload(UserBookmark.repository_group)) \
4886 .order_by(UserBookmark.position.asc()) \
4895 .order_by(UserBookmark.position.asc()) \
4887 .all()
4896 .all()
4888
4897
4889 def __unicode__(self):
4898 def __unicode__(self):
4890 return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url)
4899 return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url)
4891
4900
4892
4901
4893 class FileStore(Base, BaseModel):
4902 class FileStore(Base, BaseModel):
4894 __tablename__ = 'file_store'
4903 __tablename__ = 'file_store'
4895 __table_args__ = (
4904 __table_args__ = (
4896 base_table_args
4905 base_table_args
4897 )
4906 )
4898
4907
4899 file_store_id = Column('file_store_id', Integer(), primary_key=True)
4908 file_store_id = Column('file_store_id', Integer(), primary_key=True)
4900 file_uid = Column('file_uid', String(1024), nullable=False)
4909 file_uid = Column('file_uid', String(1024), nullable=False)
4901 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
4910 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
4902 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
4911 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
4903 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
4912 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
4904
4913
4905 # sha256 hash
4914 # sha256 hash
4906 file_hash = Column('file_hash', String(512), nullable=False)
4915 file_hash = Column('file_hash', String(512), nullable=False)
4907 file_size = Column('file_size', Integer(), nullable=False)
4916 file_size = Column('file_size', Integer(), nullable=False)
4908
4917
4909 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4918 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4910 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
4919 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
4911 accessed_count = Column('accessed_count', Integer(), default=0)
4920 accessed_count = Column('accessed_count', Integer(), default=0)
4912
4921
4913 enabled = Column('enabled', Boolean(), nullable=False, default=True)
4922 enabled = Column('enabled', Boolean(), nullable=False, default=True)
4914
4923
4915 # if repo/repo_group reference is set, check for permissions
4924 # if repo/repo_group reference is set, check for permissions
4916 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
4925 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
4917
4926
4918 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
4927 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
4919 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
4928 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
4920
4929
4921 # scope limited to user, which requester have access to
4930 # scope limited to user, which requester have access to
4922 scope_user_id = Column(
4931 scope_user_id = Column(
4923 'scope_user_id', Integer(), ForeignKey('users.user_id'),
4932 'scope_user_id', Integer(), ForeignKey('users.user_id'),
4924 nullable=True, unique=None, default=None)
4933 nullable=True, unique=None, default=None)
4925 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
4934 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
4926
4935
4927 # scope limited to user group, which requester have access to
4936 # scope limited to user group, which requester have access to
4928 scope_user_group_id = Column(
4937 scope_user_group_id = Column(
4929 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
4938 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
4930 nullable=True, unique=None, default=None)
4939 nullable=True, unique=None, default=None)
4931 user_group = relationship('UserGroup', lazy='joined')
4940 user_group = relationship('UserGroup', lazy='joined')
4932
4941
4933 # scope limited to repo, which requester have access to
4942 # scope limited to repo, which requester have access to
4934 scope_repo_id = Column(
4943 scope_repo_id = Column(
4935 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4944 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4936 nullable=True, unique=None, default=None)
4945 nullable=True, unique=None, default=None)
4937 repo = relationship('Repository', lazy='joined')
4946 repo = relationship('Repository', lazy='joined')
4938
4947
4939 # scope limited to repo group, which requester have access to
4948 # scope limited to repo group, which requester have access to
4940 scope_repo_group_id = Column(
4949 scope_repo_group_id = Column(
4941 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
4950 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
4942 nullable=True, unique=None, default=None)
4951 nullable=True, unique=None, default=None)
4943 repo_group = relationship('RepoGroup', lazy='joined')
4952 repo_group = relationship('RepoGroup', lazy='joined')
4944
4953
4945 @classmethod
4954 @classmethod
4946 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
4955 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
4947 file_description='', enabled=True, check_acl=True,
4956 file_description='', enabled=True, check_acl=True,
4948 user_id=None, scope_repo_id=None, scope_repo_group_id=None):
4957 user_id=None, scope_repo_id=None, scope_repo_group_id=None):
4949
4958
4950 store_entry = FileStore()
4959 store_entry = FileStore()
4951 store_entry.file_uid = file_uid
4960 store_entry.file_uid = file_uid
4952 store_entry.file_display_name = file_display_name
4961 store_entry.file_display_name = file_display_name
4953 store_entry.file_org_name = filename
4962 store_entry.file_org_name = filename
4954 store_entry.file_size = file_size
4963 store_entry.file_size = file_size
4955 store_entry.file_hash = file_hash
4964 store_entry.file_hash = file_hash
4956 store_entry.file_description = file_description
4965 store_entry.file_description = file_description
4957
4966
4958 store_entry.check_acl = check_acl
4967 store_entry.check_acl = check_acl
4959 store_entry.enabled = enabled
4968 store_entry.enabled = enabled
4960
4969
4961 store_entry.user_id = user_id
4970 store_entry.user_id = user_id
4962 store_entry.scope_repo_id = scope_repo_id
4971 store_entry.scope_repo_id = scope_repo_id
4963 store_entry.scope_repo_group_id = scope_repo_group_id
4972 store_entry.scope_repo_group_id = scope_repo_group_id
4964 return store_entry
4973 return store_entry
4965
4974
4966 @classmethod
4975 @classmethod
4967 def bump_access_counter(cls, file_uid, commit=True):
4976 def bump_access_counter(cls, file_uid, commit=True):
4968 FileStore().query()\
4977 FileStore().query()\
4969 .filter(FileStore.file_uid == file_uid)\
4978 .filter(FileStore.file_uid == file_uid)\
4970 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
4979 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
4971 FileStore.accessed_on: datetime.datetime.now()})
4980 FileStore.accessed_on: datetime.datetime.now()})
4972 if commit:
4981 if commit:
4973 Session().commit()
4982 Session().commit()
4974
4983
4975 def __repr__(self):
4984 def __repr__(self):
4976 return '<FileStore({})>'.format(self.file_store_id)
4985 return '<FileStore({})>'.format(self.file_store_id)
4977
4986
4978
4987
4979 class DbMigrateVersion(Base, BaseModel):
4988 class DbMigrateVersion(Base, BaseModel):
4980 __tablename__ = 'db_migrate_version'
4989 __tablename__ = 'db_migrate_version'
4981 __table_args__ = (
4990 __table_args__ = (
4982 base_table_args,
4991 base_table_args,
4983 )
4992 )
4984
4993
4985 repository_id = Column('repository_id', String(250), primary_key=True)
4994 repository_id = Column('repository_id', String(250), primary_key=True)
4986 repository_path = Column('repository_path', Text)
4995 repository_path = Column('repository_path', Text)
4987 version = Column('version', Integer)
4996 version = Column('version', Integer)
4988
4997
4989 @classmethod
4998 @classmethod
4990 def set_version(cls, version):
4999 def set_version(cls, version):
4991 """
5000 """
4992 Helper for forcing a different version, usually for debugging purposes via ishell.
5001 Helper for forcing a different version, usually for debugging purposes via ishell.
4993 """
5002 """
4994 ver = DbMigrateVersion.query().first()
5003 ver = DbMigrateVersion.query().first()
4995 ver.version = version
5004 ver.version = version
4996 Session().commit()
5005 Session().commit()
4997
5006
4998
5007
4999 class DbSession(Base, BaseModel):
5008 class DbSession(Base, BaseModel):
5000 __tablename__ = 'db_session'
5009 __tablename__ = 'db_session'
5001 __table_args__ = (
5010 __table_args__ = (
5002 base_table_args,
5011 base_table_args,
5003 )
5012 )
5004
5013
5005 def __repr__(self):
5014 def __repr__(self):
5006 return '<DB:DbSession({})>'.format(self.id)
5015 return '<DB:DbSession({})>'.format(self.id)
5007
5016
5008 id = Column('id', Integer())
5017 id = Column('id', Integer())
5009 namespace = Column('namespace', String(255), primary_key=True)
5018 namespace = Column('namespace', String(255), primary_key=True)
5010 accessed = Column('accessed', DateTime, nullable=False)
5019 accessed = Column('accessed', DateTime, nullable=False)
5011 created = Column('created', DateTime, nullable=False)
5020 created = Column('created', DateTime, nullable=False)
5012 data = Column('data', PickleType, nullable=False)
5021 data = Column('data', PickleType, nullable=False)
General Comments 0
You need to be logged in to leave comments. Login now