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