##// END OF EJS Templates
fixed some bugs in api key auth, added access by api key into rss/atom feeds in global journal...
marcink -
r1120:a8d75961 beta
parent child Browse files
Show More
@@ -1,87 +1,87 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.feed
3 rhodecode.controllers.feed
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Feed controller for rhodecode
6 Feed controller for rhodecode
7
7
8 :created_on: Apr 23, 2010
8 :created_on: Apr 23, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import logging
28 import logging
29
29
30 from pylons import url, response, tmpl_context as c
30 from pylons import url, response, tmpl_context as c
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib.base import BaseRepoController
34 from rhodecode.lib.base import BaseRepoController
35
35
36 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
36 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40 class FeedController(BaseRepoController):
40 class FeedController(BaseRepoController):
41
41
42 @LoginRequired()
42 @LoginRequired(api_access=True)
43 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
43 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
44 'repository.admin')
44 'repository.admin')
45 def __before__(self):
45 def __before__(self):
46 super(FeedController, self).__before__()
46 super(FeedController, self).__before__()
47 #common values for feeds
47 #common values for feeds
48 self.description = _('Changes on %s repository')
48 self.description = _('Changes on %s repository')
49 self.title = self.title = _('%s %s feed') % (c.rhodecode_name, '%s')
49 self.title = self.title = _('%s %s feed') % (c.rhodecode_name, '%s')
50 self.language = 'en-us'
50 self.language = 'en-us'
51 self.ttl = "5"
51 self.ttl = "5"
52 self.feed_nr = 10
52 self.feed_nr = 10
53
53
54 def atom(self, repo_name):
54 def atom(self, repo_name):
55 """Produce an atom-1.0 feed via feedgenerator module"""
55 """Produce an atom-1.0 feed via feedgenerator module"""
56 feed = Atom1Feed(title=self.title % repo_name,
56 feed = Atom1Feed(title=self.title % repo_name,
57 link=url('summary_home', repo_name=repo_name, qualified=True),
57 link=url('summary_home', repo_name=repo_name, qualified=True),
58 description=self.description % repo_name,
58 description=self.description % repo_name,
59 language=self.language,
59 language=self.language,
60 ttl=self.ttl)
60 ttl=self.ttl)
61
61
62 for cs in c.rhodecode_repo[:self.feed_nr]:
62 for cs in c.rhodecode_repo[:self.feed_nr]:
63 feed.add_item(title=cs.message,
63 feed.add_item(title=cs.message,
64 link=url('changeset_home', repo_name=repo_name,
64 link=url('changeset_home', repo_name=repo_name,
65 revision=cs.raw_id, qualified=True),
65 revision=cs.raw_id, qualified=True),
66 description=str(cs.date))
66 description=str(cs.date))
67
67
68 response.content_type = feed.mime_type
68 response.content_type = feed.mime_type
69 return feed.writeString('utf-8')
69 return feed.writeString('utf-8')
70
70
71
71
72 def rss(self, repo_name):
72 def rss(self, repo_name):
73 """Produce an rss2 feed via feedgenerator module"""
73 """Produce an rss2 feed via feedgenerator module"""
74 feed = Rss201rev2Feed(title=self.title % repo_name,
74 feed = Rss201rev2Feed(title=self.title % repo_name,
75 link=url('summary_home', repo_name=repo_name, qualified=True),
75 link=url('summary_home', repo_name=repo_name, qualified=True),
76 description=self.description % repo_name,
76 description=self.description % repo_name,
77 language=self.language,
77 language=self.language,
78 ttl=self.ttl)
78 ttl=self.ttl)
79
79
80 for cs in c.rhodecode_repo[:self.feed_nr]:
80 for cs in c.rhodecode_repo[:self.feed_nr]:
81 feed.add_item(title=cs.message,
81 feed.add_item(title=cs.message,
82 link=url('changeset_home', repo_name=repo_name,
82 link=url('changeset_home', repo_name=repo_name,
83 revision=cs.raw_id, qualified=True),
83 revision=cs.raw_id, qualified=True),
84 description=str(cs.date))
84 description=str(cs.date))
85
85
86 response.content_type = feed.mime_type
86 response.content_type = feed.mime_type
87 return feed.writeString('utf-8')
87 return feed.writeString('utf-8')
@@ -1,245 +1,248 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.journal
3 rhodecode.controllers.journal
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Journal controller for pylons
6 Journal controller for pylons
7
7
8 :created_on: Nov 21, 2010
8 :created_on: Nov 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27 import logging
27 import logging
28
28
29 from sqlalchemy import or_
29 from sqlalchemy import or_
30 from sqlalchemy.orm import joinedload, make_transient
30 from sqlalchemy.orm import joinedload, make_transient
31 from webhelpers.paginate import Page
31 from webhelpers.paginate import Page
32 from itertools import groupby
32 from itertools import groupby
33
33
34 from paste.httpexceptions import HTTPInternalServerError
34 from paste.httpexceptions import HTTPInternalServerError
35 from pylons import request, tmpl_context as c, response, url
35 from pylons import request, tmpl_context as c, response, url
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
37 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
38
38
39 import rhodecode.lib.helpers as h
39 import rhodecode.lib.helpers as h
40 from rhodecode.lib.auth import LoginRequired, NotAnonymous
40 from rhodecode.lib.auth import LoginRequired, NotAnonymous
41 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.base import BaseController, render
42 from rhodecode.model.db import UserLog, UserFollowing
42 from rhodecode.model.db import UserLog, UserFollowing
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46 class JournalController(BaseController):
46 class JournalController(BaseController):
47
47
48
48
49 @LoginRequired()
49
50 def __before__(self):
50 def __before__(self):
51 super(JournalController, self).__before__()
51 super(JournalController, self).__before__()
52 c.rhodecode_user = self.rhodecode_user
52 c.rhodecode_user = self.rhodecode_user
53 self.title = _('%s public journal %s feed') % (c.rhodecode_name, '%s')
53 self.title = _('%s public journal %s feed') % (c.rhodecode_name, '%s')
54 self.language = 'en-us'
54 self.language = 'en-us'
55 self.ttl = "5"
55 self.ttl = "5"
56 self.feed_nr = 20
56 self.feed_nr = 20
57
57
58 @LoginRequired()
58 @NotAnonymous()
59 @NotAnonymous()
59 def index(self):
60 def index(self):
60 # Return a rendered template
61 # Return a rendered template
61 p = int(request.params.get('page', 1))
62 p = int(request.params.get('page', 1))
62
63
63 c.following = self.sa.query(UserFollowing)\
64 c.following = self.sa.query(UserFollowing)\
64 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
65 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
65 .options(joinedload(UserFollowing.follows_repository))\
66 .options(joinedload(UserFollowing.follows_repository))\
66 .all()
67 .all()
67
68
68 journal = self._get_journal_data(c.following)
69 journal = self._get_journal_data(c.following)
69
70
70 c.journal_pager = Page(journal, page=p, items_per_page=20)
71 c.journal_pager = Page(journal, page=p, items_per_page=20)
71
72
72 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
73 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
73
74
74 c.journal_data = render('journal/journal_data.html')
75 c.journal_data = render('journal/journal_data.html')
75 if request.params.get('partial'):
76 if request.params.get('partial'):
76 return c.journal_data
77 return c.journal_data
77 return render('journal/journal.html')
78 return render('journal/journal.html')
78
79
79
80
80 def _get_daily_aggregate(self, journal):
81 def _get_daily_aggregate(self, journal):
81 groups = []
82 groups = []
82 for k, g in groupby(journal, lambda x:x.action_as_day):
83 for k, g in groupby(journal, lambda x:x.action_as_day):
83 user_group = []
84 user_group = []
84 for k2, g2 in groupby(list(g), lambda x:x.user.email):
85 for k2, g2 in groupby(list(g), lambda x:x.user.email):
85 l = list(g2)
86 l = list(g2)
86 user_group.append((l[0].user, l))
87 user_group.append((l[0].user, l))
87
88
88 groups.append((k, user_group,))
89 groups.append((k, user_group,))
89
90
90 return groups
91 return groups
91
92
92
93
93 def _get_journal_data(self, following_repos):
94 def _get_journal_data(self, following_repos):
94 repo_ids = [x.follows_repository.repo_id for x in following_repos
95 repo_ids = [x.follows_repository.repo_id for x in following_repos
95 if x.follows_repository is not None]
96 if x.follows_repository is not None]
96 user_ids = [x.follows_user.user_id for x in following_repos
97 user_ids = [x.follows_user.user_id for x in following_repos
97 if x.follows_user is not None]
98 if x.follows_user is not None]
98
99
99 filtering_criterion = None
100 filtering_criterion = None
100
101
101 if repo_ids and user_ids:
102 if repo_ids and user_ids:
102 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
103 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
103 UserLog.user_id.in_(user_ids))
104 UserLog.user_id.in_(user_ids))
104 if repo_ids and not user_ids:
105 if repo_ids and not user_ids:
105 filtering_criterion = UserLog.repository_id.in_(repo_ids)
106 filtering_criterion = UserLog.repository_id.in_(repo_ids)
106 if not repo_ids and user_ids:
107 if not repo_ids and user_ids:
107 filtering_criterion = UserLog.user_id.in_(user_ids)
108 filtering_criterion = UserLog.user_id.in_(user_ids)
108 if filtering_criterion is not None:
109 if filtering_criterion is not None:
109 journal = self.sa.query(UserLog)\
110 journal = self.sa.query(UserLog)\
110 .options(joinedload(UserLog.user))\
111 .options(joinedload(UserLog.user))\
111 .options(joinedload(UserLog.repository))\
112 .options(joinedload(UserLog.repository))\
112 .filter(filtering_criterion)\
113 .filter(filtering_criterion)\
113 .order_by(UserLog.action_date.desc())
114 .order_by(UserLog.action_date.desc())
114 else:
115 else:
115 journal = []
116 journal = []
116
117
117
118
118 return journal
119 return journal
119
120
121 @LoginRequired()
120 @NotAnonymous()
122 @NotAnonymous()
121 def toggle_following(self):
123 def toggle_following(self):
122 cur_token = request.POST.get('auth_token')
124 cur_token = request.POST.get('auth_token')
123 token = h.get_token()
125 token = h.get_token()
124 if cur_token == token:
126 if cur_token == token:
125
127
126 user_id = request.POST.get('follows_user_id')
128 user_id = request.POST.get('follows_user_id')
127 if user_id:
129 if user_id:
128 try:
130 try:
129 self.scm_model.toggle_following_user(user_id,
131 self.scm_model.toggle_following_user(user_id,
130 self.rhodecode_user.user_id)
132 self.rhodecode_user.user_id)
131 return 'ok'
133 return 'ok'
132 except:
134 except:
133 raise HTTPInternalServerError()
135 raise HTTPInternalServerError()
134
136
135 repo_id = request.POST.get('follows_repo_id')
137 repo_id = request.POST.get('follows_repo_id')
136 if repo_id:
138 if repo_id:
137 try:
139 try:
138 self.scm_model.toggle_following_repo(repo_id,
140 self.scm_model.toggle_following_repo(repo_id,
139 self.rhodecode_user.user_id)
141 self.rhodecode_user.user_id)
140 return 'ok'
142 return 'ok'
141 except:
143 except:
142 raise HTTPInternalServerError()
144 raise HTTPInternalServerError()
143
145
144
146
145 log.debug('token mismatch %s vs %s', cur_token, token)
147 log.debug('token mismatch %s vs %s', cur_token, token)
146 raise HTTPInternalServerError()
148 raise HTTPInternalServerError()
147
149
148
150
149
151
150
152 @LoginRequired()
151 def public_journal(self):
153 def public_journal(self):
152 # Return a rendered template
154 # Return a rendered template
153 p = int(request.params.get('page', 1))
155 p = int(request.params.get('page', 1))
154
156
155 c.following = self.sa.query(UserFollowing)\
157 c.following = self.sa.query(UserFollowing)\
156 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
158 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
157 .options(joinedload(UserFollowing.follows_repository))\
159 .options(joinedload(UserFollowing.follows_repository))\
158 .all()
160 .all()
159
161
160 journal = self._get_journal_data(c.following)
162 journal = self._get_journal_data(c.following)
161
163
162 c.journal_pager = Page(journal, page=p, items_per_page=20)
164 c.journal_pager = Page(journal, page=p, items_per_page=20)
163
165
164 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
166 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
165
167
166 c.journal_data = render('journal/journal_data.html')
168 c.journal_data = render('journal/journal_data.html')
167 if request.params.get('partial'):
169 if request.params.get('partial'):
168 return c.journal_data
170 return c.journal_data
169 return render('journal/public_journal.html')
171 return render('journal/public_journal.html')
170
172
171
173
172
174 @LoginRequired(api_access=True)
173 def public_journal_atom(self):
175 def public_journal_atom(self):
174 """
176 """
175 Produce an atom-1.0 feed via feedgenerator module
177 Produce an atom-1.0 feed via feedgenerator module
176 """
178 """
177 c.following = self.sa.query(UserFollowing)\
179 c.following = self.sa.query(UserFollowing)\
178 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
180 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
179 .options(joinedload(UserFollowing.follows_repository))\
181 .options(joinedload(UserFollowing.follows_repository))\
180 .all()
182 .all()
181
183
182 journal = self._get_journal_data(c.following)
184 journal = self._get_journal_data(c.following)
183
185
184 feed = Atom1Feed(title=self.title % 'atom',
186 feed = Atom1Feed(title=self.title % 'atom',
185 link=url('public_journal_atom', qualified=True),
187 link=url('public_journal_atom', qualified=True),
186 description=_('Public journal'),
188 description=_('Public journal'),
187 language=self.language,
189 language=self.language,
188 ttl=self.ttl)
190 ttl=self.ttl)
189
191
190 for entry in journal[:self.feed_nr]:
192 for entry in journal[:self.feed_nr]:
191 #tmpl = h.action_parser(entry)[0]
193 #tmpl = h.action_parser(entry)[0]
192 action, action_extra = h.action_parser(entry, feed=True)
194 action, action_extra = h.action_parser(entry, feed=True)
193 title = "%s - %s %s" % (entry.user.short_contact, action,
195 title = "%s - %s %s" % (entry.user.short_contact, action,
194 entry.repository.repo_name)
196 entry.repository.repo_name)
195 desc = action_extra()
197 desc = action_extra()
196 feed.add_item(title=title,
198 feed.add_item(title=title,
197 pubdate=entry.action_date,
199 pubdate=entry.action_date,
198 link=url('', qualified=True),
200 link=url('', qualified=True),
199 author_email=entry.user.email,
201 author_email=entry.user.email,
200 author_name=entry.user.full_contact,
202 author_name=entry.user.full_contact,
201 description=desc)
203 description=desc)
202
204
203 response.content_type = feed.mime_type
205 response.content_type = feed.mime_type
204 return feed.writeString('utf-8')
206 return feed.writeString('utf-8')
205
207
208 @LoginRequired(api_access=True)
206 def public_journal_rss(self):
209 def public_journal_rss(self):
207 """
210 """
208 Produce an rss2 feed via feedgenerator module
211 Produce an rss2 feed via feedgenerator module
209 """
212 """
210 c.following = self.sa.query(UserFollowing)\
213 c.following = self.sa.query(UserFollowing)\
211 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
214 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
212 .options(joinedload(UserFollowing.follows_repository))\
215 .options(joinedload(UserFollowing.follows_repository))\
213 .all()
216 .all()
214
217
215 journal = self._get_journal_data(c.following)
218 journal = self._get_journal_data(c.following)
216
219
217 feed = Rss201rev2Feed(title=self.title % 'rss',
220 feed = Rss201rev2Feed(title=self.title % 'rss',
218 link=url('public_journal_rss', qualified=True),
221 link=url('public_journal_rss', qualified=True),
219 description=_('Public journal'),
222 description=_('Public journal'),
220 language=self.language,
223 language=self.language,
221 ttl=self.ttl)
224 ttl=self.ttl)
222
225
223 for entry in journal[:self.feed_nr]:
226 for entry in journal[:self.feed_nr]:
224 #tmpl = h.action_parser(entry)[0]
227 #tmpl = h.action_parser(entry)[0]
225 action, action_extra = h.action_parser(entry, feed=True)
228 action, action_extra = h.action_parser(entry, feed=True)
226 title = "%s - %s %s" % (entry.user.short_contact, action,
229 title = "%s - %s %s" % (entry.user.short_contact, action,
227 entry.repository.repo_name)
230 entry.repository.repo_name)
228 desc = action_extra()
231 desc = action_extra()
229 feed.add_item(title=title,
232 feed.add_item(title=title,
230 pubdate=entry.action_date,
233 pubdate=entry.action_date,
231 link=url('', qualified=True),
234 link=url('', qualified=True),
232 author_email=entry.user.email,
235 author_email=entry.user.email,
233 author_name=entry.user.full_contact,
236 author_name=entry.user.full_contact,
234 description=desc)
237 description=desc)
235
238
236 response.content_type = feed.mime_type
239 response.content_type = feed.mime_type
237 return feed.writeString('utf-8')
240 return feed.writeString('utf-8')
238
241
239
242
240
243
241
244
242
245
243
246
244
247
245
248
@@ -1,591 +1,591 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.auth
3 rhodecode.lib.auth
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 authentication and permission libraries
6 authentication and permission libraries
7
7
8 :created_on: Apr 4, 2010
8 :created_on: Apr 4, 2010
9 :copyright: (c) 2010 by marcink.
9 :copyright: (c) 2010 by marcink.
10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
11 """
11 """
12 # This program is free software; you can redistribute it and/or
12 # This program is free software; you can redistribute it and/or
13 # modify it under the terms of the GNU General Public License
13 # modify it under the terms of the GNU General Public License
14 # as published by the Free Software Foundation; version 2
14 # as published by the Free Software Foundation; version 2
15 # of the License or (at your opinion) any later version of the license.
15 # of the License or (at your opinion) any later version of the license.
16 #
16 #
17 # This program is distributed in the hope that it will be useful,
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
20 # GNU General Public License for more details.
21 #
21 #
22 # You should have received a copy of the GNU General Public License
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
24 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # MA 02110-1301, USA.
25 # MA 02110-1301, USA.
26
26
27 import random
27 import random
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import hashlib
30 import hashlib
31
31
32 from tempfile import _RandomNameSequence
32 from tempfile import _RandomNameSequence
33 from decorator import decorator
33 from decorator import decorator
34
34
35 from pylons import config, session, url, request
35 from pylons import config, session, url, request
36 from pylons.controllers.util import abort, redirect
36 from pylons.controllers.util import abort, redirect
37 from pylons.i18n.translation import _
37 from pylons.i18n.translation import _
38
38
39 from rhodecode import __platform__
39 from rhodecode import __platform__
40
40
41 if __platform__ == 'Windows':
41 if __platform__ == 'Windows':
42 from hashlib import sha256
42 from hashlib import sha256
43 if __platform__ in ('Linux', 'Darwin'):
43 if __platform__ in ('Linux', 'Darwin'):
44 import bcrypt
44 import bcrypt
45
45
46
46
47 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
47 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
48 from rhodecode.lib.utils import get_repo_slug
48 from rhodecode.lib.utils import get_repo_slug
49 from rhodecode.lib.auth_ldap import AuthLdap
49 from rhodecode.lib.auth_ldap import AuthLdap
50
50
51 from rhodecode.model import meta
51 from rhodecode.model import meta
52 from rhodecode.model.user import UserModel
52 from rhodecode.model.user import UserModel
53 from rhodecode.model.db import Permission
53 from rhodecode.model.db import Permission
54
54
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58 class PasswordGenerator(object):
58 class PasswordGenerator(object):
59 """This is a simple class for generating password from
59 """This is a simple class for generating password from
60 different sets of characters
60 different sets of characters
61 usage:
61 usage:
62 passwd_gen = PasswordGenerator()
62 passwd_gen = PasswordGenerator()
63 #print 8-letter password containing only big and small letters of alphabet
63 #print 8-letter password containing only big and small letters of alphabet
64 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
64 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
65 """
65 """
66 ALPHABETS_NUM = r'''1234567890'''#[0]
66 ALPHABETS_NUM = r'''1234567890'''#[0]
67 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''#[1]
67 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''#[1]
68 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''#[2]
68 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''#[2]
69 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?''' #[3]
69 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?''' #[3]
70 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM + ALPHABETS_SPECIAL#[4]
70 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM + ALPHABETS_SPECIAL#[4]
71 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM#[5]
71 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM#[5]
72 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
72 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
73 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM#[6]
73 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM#[6]
74 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM#[7]
74 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM#[7]
75
75
76 def __init__(self, passwd=''):
76 def __init__(self, passwd=''):
77 self.passwd = passwd
77 self.passwd = passwd
78
78
79 def gen_password(self, len, type):
79 def gen_password(self, len, type):
80 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
80 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
81 return self.passwd
81 return self.passwd
82
82
83 class RhodeCodeCrypto(object):
83 class RhodeCodeCrypto(object):
84
84
85 @classmethod
85 @classmethod
86 def hash_string(cls, str_):
86 def hash_string(cls, str_):
87 """
87 """
88 Cryptographic function used for password hashing based on pybcrypt
88 Cryptographic function used for password hashing based on pybcrypt
89 or pycrypto in windows
89 or pycrypto in windows
90
90
91 :param password: password to hash
91 :param password: password to hash
92 """
92 """
93 if __platform__ == 'Windows':
93 if __platform__ == 'Windows':
94 return sha256(str_).hexdigest()
94 return sha256(str_).hexdigest()
95 elif __platform__ in ('Linux', 'Darwin'):
95 elif __platform__ in ('Linux', 'Darwin'):
96 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
96 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
97 else:
97 else:
98 raise Exception('Unknown or unsupoprted platform %s' % __platform__)
98 raise Exception('Unknown or unsupoprted platform %s' % __platform__)
99
99
100 @classmethod
100 @classmethod
101 def hash_check(cls, password, hashed):
101 def hash_check(cls, password, hashed):
102 """
102 """
103 Checks matching password with it's hashed value, runs different
103 Checks matching password with it's hashed value, runs different
104 implementation based on platform it runs on
104 implementation based on platform it runs on
105
105
106 :param password: password
106 :param password: password
107 :param hashed: password in hashed form
107 :param hashed: password in hashed form
108 """
108 """
109
109
110 if __platform__ == 'Windows':
110 if __platform__ == 'Windows':
111 return sha256(password).hexdigest() == hashed
111 return sha256(password).hexdigest() == hashed
112 elif __platform__ in ('Linux', 'Darwin'):
112 elif __platform__ in ('Linux', 'Darwin'):
113 return bcrypt.hashpw(password, hashed) == hashed
113 return bcrypt.hashpw(password, hashed) == hashed
114 else:
114 else:
115 raise Exception('Unknown or unsupoprted platform %s' % __platform__)
115 raise Exception('Unknown or unsupoprted platform %s' % __platform__)
116
116
117
117
118
118
119
119
120
120
121 def get_crypt_password(password):
121 def get_crypt_password(password):
122 return RhodeCodeCrypto.hash_string(password)
122 return RhodeCodeCrypto.hash_string(password)
123
123
124 def check_password(password, hashed):
124 def check_password(password, hashed):
125 return RhodeCodeCrypto.hash_check(password, hashed)
125 return RhodeCodeCrypto.hash_check(password, hashed)
126
126
127 def generate_api_key(username, salt=None):
127 def generate_api_key(username, salt=None):
128 if salt is None:
128 if salt is None:
129 salt = _RandomNameSequence().next()
129 salt = _RandomNameSequence().next()
130
130
131 return hashlib.sha1(username + salt).hexdigest()
131 return hashlib.sha1(username + salt).hexdigest()
132
132
133 def authfunc(environ, username, password):
133 def authfunc(environ, username, password):
134 """Dummy authentication function used in Mercurial/Git/ and access control,
134 """Dummy authentication function used in Mercurial/Git/ and access control,
135
135
136 :param environ: needed only for using in Basic auth
136 :param environ: needed only for using in Basic auth
137 """
137 """
138 return authenticate(username, password)
138 return authenticate(username, password)
139
139
140
140
141 def authenticate(username, password):
141 def authenticate(username, password):
142 """Authentication function used for access control,
142 """Authentication function used for access control,
143 firstly checks for db authentication then if ldap is enabled for ldap
143 firstly checks for db authentication then if ldap is enabled for ldap
144 authentication, also creates ldap user if not in database
144 authentication, also creates ldap user if not in database
145
145
146 :param username: username
146 :param username: username
147 :param password: password
147 :param password: password
148 """
148 """
149 user_model = UserModel()
149 user_model = UserModel()
150 user = user_model.get_by_username(username, cache=False)
150 user = user_model.get_by_username(username, cache=False)
151
151
152 log.debug('Authenticating user using RhodeCode account')
152 log.debug('Authenticating user using RhodeCode account')
153 if user is not None and not user.ldap_dn:
153 if user is not None and not user.ldap_dn:
154 if user.active:
154 if user.active:
155
155
156 if user.username == 'default' and user.active:
156 if user.username == 'default' and user.active:
157 log.info('user %s authenticated correctly as anonymous user',
157 log.info('user %s authenticated correctly as anonymous user',
158 username)
158 username)
159 return True
159 return True
160
160
161 elif user.username == username and check_password(password, user.password):
161 elif user.username == username and check_password(password, user.password):
162 log.info('user %s authenticated correctly', username)
162 log.info('user %s authenticated correctly', username)
163 return True
163 return True
164 else:
164 else:
165 log.warning('user %s is disabled', username)
165 log.warning('user %s is disabled', username)
166
166
167 else:
167 else:
168 log.debug('Regular authentication failed')
168 log.debug('Regular authentication failed')
169 user_obj = user_model.get_by_username(username, cache=False,
169 user_obj = user_model.get_by_username(username, cache=False,
170 case_insensitive=True)
170 case_insensitive=True)
171
171
172 if user_obj is not None and not user_obj.ldap_dn:
172 if user_obj is not None and not user_obj.ldap_dn:
173 log.debug('this user already exists as non ldap')
173 log.debug('this user already exists as non ldap')
174 return False
174 return False
175
175
176 from rhodecode.model.settings import SettingsModel
176 from rhodecode.model.settings import SettingsModel
177 ldap_settings = SettingsModel().get_ldap_settings()
177 ldap_settings = SettingsModel().get_ldap_settings()
178
178
179 #======================================================================
179 #======================================================================
180 # FALLBACK TO LDAP AUTH IF ENABLE
180 # FALLBACK TO LDAP AUTH IF ENABLE
181 #======================================================================
181 #======================================================================
182 if ldap_settings.get('ldap_active', False):
182 if ldap_settings.get('ldap_active', False):
183 log.debug("Authenticating user using ldap")
183 log.debug("Authenticating user using ldap")
184 kwargs = {
184 kwargs = {
185 'server':ldap_settings.get('ldap_host', ''),
185 'server':ldap_settings.get('ldap_host', ''),
186 'base_dn':ldap_settings.get('ldap_base_dn', ''),
186 'base_dn':ldap_settings.get('ldap_base_dn', ''),
187 'port':ldap_settings.get('ldap_port'),
187 'port':ldap_settings.get('ldap_port'),
188 'bind_dn':ldap_settings.get('ldap_dn_user'),
188 'bind_dn':ldap_settings.get('ldap_dn_user'),
189 'bind_pass':ldap_settings.get('ldap_dn_pass'),
189 'bind_pass':ldap_settings.get('ldap_dn_pass'),
190 'use_ldaps':ldap_settings.get('ldap_ldaps'),
190 'use_ldaps':ldap_settings.get('ldap_ldaps'),
191 'tls_reqcert':ldap_settings.get('ldap_tls_reqcert'),
191 'tls_reqcert':ldap_settings.get('ldap_tls_reqcert'),
192 'ldap_filter':ldap_settings.get('ldap_filter'),
192 'ldap_filter':ldap_settings.get('ldap_filter'),
193 'search_scope':ldap_settings.get('ldap_search_scope'),
193 'search_scope':ldap_settings.get('ldap_search_scope'),
194 'attr_login':ldap_settings.get('ldap_attr_login'),
194 'attr_login':ldap_settings.get('ldap_attr_login'),
195 'ldap_version':3,
195 'ldap_version':3,
196 }
196 }
197 log.debug('Checking for ldap authentication')
197 log.debug('Checking for ldap authentication')
198 try:
198 try:
199 aldap = AuthLdap(**kwargs)
199 aldap = AuthLdap(**kwargs)
200 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, password)
200 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, password)
201 log.debug('Got ldap DN response %s', user_dn)
201 log.debug('Got ldap DN response %s', user_dn)
202
202
203 user_attrs = {
203 user_attrs = {
204 'name' : ldap_attrs[ldap_settings.get('ldap_attr_firstname')][0],
204 'name' : ldap_attrs[ldap_settings.get('ldap_attr_firstname')][0],
205 'lastname' : ldap_attrs[ldap_settings.get('ldap_attr_lastname')][0],
205 'lastname' : ldap_attrs[ldap_settings.get('ldap_attr_lastname')][0],
206 'email' : ldap_attrs[ldap_settings.get('ldap_attr_email')][0],
206 'email' : ldap_attrs[ldap_settings.get('ldap_attr_email')][0],
207 }
207 }
208
208
209 if user_model.create_ldap(username, password, user_dn, user_attrs):
209 if user_model.create_ldap(username, password, user_dn, user_attrs):
210 log.info('created new ldap user %s', username)
210 log.info('created new ldap user %s', username)
211
211
212 return True
212 return True
213 except (LdapUsernameError, LdapPasswordError,):
213 except (LdapUsernameError, LdapPasswordError,):
214 pass
214 pass
215 except (Exception,):
215 except (Exception,):
216 log.error(traceback.format_exc())
216 log.error(traceback.format_exc())
217 pass
217 pass
218 return False
218 return False
219
219
220 class AuthUser(object):
220 class AuthUser(object):
221 """
221 """
222 A simple object that handles all attributes of user in RhodeCode
222 A simple object that handles all attributes of user in RhodeCode
223
223
224 It does lookup based on API key,given user, or user present in session
224 It does lookup based on API key,given user, or user present in session
225 Then it fills all required information for such user. It also checks if
225 Then it fills all required information for such user. It also checks if
226 anonymous access is enabled and if so, it returns default user as logged
226 anonymous access is enabled and if so, it returns default user as logged
227 in
227 in
228 """
228 """
229
229
230 def __init__(self, user_id=None, api_key=None):
230 def __init__(self, user_id=None, api_key=None):
231
231
232 self.user_id = user_id
232 self.user_id = user_id
233 self.api_key = api_key
233 self.api_key = None
234
234
235 self.username = 'None'
235 self.username = 'None'
236 self.name = ''
236 self.name = ''
237 self.lastname = ''
237 self.lastname = ''
238 self.email = ''
238 self.email = ''
239 self.is_authenticated = False
239 self.is_authenticated = False
240 self.admin = False
240 self.admin = False
241 self.permissions = {}
241 self.permissions = {}
242 self._api_key = api_key
242 self.propagate_data()
243 self.propagate_data()
243
244
244
245
245 def propagate_data(self):
246 def propagate_data(self):
246 user_model = UserModel()
247 user_model = UserModel()
247 if self.api_key:
248 self.anonymous_user = user_model.get_by_username('default', cache=True)
249 if self._api_key:
248 #try go get user by api key
250 #try go get user by api key
249 log.debug('Auth User lookup by API KEY %s', self.api_key)
251 log.debug('Auth User lookup by API KEY %s', self._api_key)
250 user_model.fill_data(self, api_key=self.api_key)
252 user_model.fill_data(self, api_key=self._api_key)
251 else:
253 else:
252 log.debug('Auth User lookup by USER ID %s', self.user_id)
254 log.debug('Auth User lookup by USER ID %s', self.user_id)
253 self.anonymous_user = user_model.get_by_username('default', cache=True)
254
255 if self.user_id is not None and self.user_id != self.anonymous_user.user_id:
255 if self.user_id is not None and self.user_id != self.anonymous_user.user_id:
256 user_model.fill_data(self, user_id=self.user_id)
256 user_model.fill_data(self, user_id=self.user_id)
257 else:
257 else:
258 if self.anonymous_user.active is True:
258 if self.anonymous_user.active is True:
259 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
259 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
260 #then we set this user is logged in
260 #then we set this user is logged in
261 self.is_authenticated = True
261 self.is_authenticated = True
262 else:
262 else:
263 self.is_authenticated = False
263 self.is_authenticated = False
264
264
265 log.debug('Auth User is now %s', self)
265 log.debug('Auth User is now %s', self)
266 user_model.fill_perms(self)
266 user_model.fill_perms(self)
267
267
268 @property
268 @property
269 def is_admin(self):
269 def is_admin(self):
270 return self.admin
270 return self.admin
271
271
272 def __repr__(self):
272 def __repr__(self):
273 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
273 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
274 self.is_authenticated)
274 self.is_authenticated)
275
275
276 def set_authenticated(self, authenticated=True):
276 def set_authenticated(self, authenticated=True):
277
277
278 if self.user_id != self.anonymous_user.user_id:
278 if self.user_id != self.anonymous_user.user_id:
279 self.is_authenticated = authenticated
279 self.is_authenticated = authenticated
280
280
281
281
282 def set_available_permissions(config):
282 def set_available_permissions(config):
283 """This function will propagate pylons globals with all available defined
283 """This function will propagate pylons globals with all available defined
284 permission given in db. We don't want to check each time from db for new
284 permission given in db. We don't want to check each time from db for new
285 permissions since adding a new permission also requires application restart
285 permissions since adding a new permission also requires application restart
286 ie. to decorate new views with the newly created permission
286 ie. to decorate new views with the newly created permission
287
287
288 :param config: current pylons config instance
288 :param config: current pylons config instance
289
289
290 """
290 """
291 log.info('getting information about all available permissions')
291 log.info('getting information about all available permissions')
292 try:
292 try:
293 sa = meta.Session()
293 sa = meta.Session()
294 all_perms = sa.query(Permission).all()
294 all_perms = sa.query(Permission).all()
295 except:
295 except:
296 pass
296 pass
297 finally:
297 finally:
298 meta.Session.remove()
298 meta.Session.remove()
299
299
300 config['available_permissions'] = [x.permission_name for x in all_perms]
300 config['available_permissions'] = [x.permission_name for x in all_perms]
301
301
302 #===============================================================================
302 #===============================================================================
303 # CHECK DECORATORS
303 # CHECK DECORATORS
304 #===============================================================================
304 #===============================================================================
305 class LoginRequired(object):
305 class LoginRequired(object):
306 """
306 """
307 Must be logged in to execute this function else
307 Must be logged in to execute this function else
308 redirect to login page
308 redirect to login page
309
309
310 :param api_access: if enabled this checks only for valid auth token
310 :param api_access: if enabled this checks only for valid auth token
311 and grants access based on valid token
311 and grants access based on valid token
312 """
312 """
313
313
314 def __init__(self, api_access=False):
314 def __init__(self, api_access=False):
315 self.api_access = api_access
315 self.api_access = api_access
316
316
317 def __call__(self, func):
317 def __call__(self, func):
318 return decorator(self.__wrapper, func)
318 return decorator(self.__wrapper, func)
319
319
320 def __wrapper(self, func, *fargs, **fkwargs):
320 def __wrapper(self, func, *fargs, **fkwargs):
321 cls = fargs[0]
321 cls = fargs[0]
322 user = cls.rhodecode_user
322 user = cls.rhodecode_user
323
323
324 api_access_ok = False
324 api_access_ok = False
325 if self.api_access:
325 if self.api_access:
326 log.debug('Checking API KEY access for %s', cls)
326 log.debug('Checking API KEY access for %s', cls)
327 if user.api_key == request.GET.get('api_key'):
327 if user.api_key == request.GET.get('api_key'):
328 api_access_ok = True
328 api_access_ok = True
329 else:
329 else:
330 log.debug("API KEY token not valid")
330 log.debug("API KEY token not valid")
331
331
332 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
332 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
333 if user.is_authenticated or api_access_ok:
333 if user.is_authenticated or api_access_ok:
334 log.debug('user %s is authenticated', user.username)
334 log.debug('user %s is authenticated', user.username)
335 return func(*fargs, **fkwargs)
335 return func(*fargs, **fkwargs)
336 else:
336 else:
337 log.warn('user %s NOT authenticated', user.username)
337 log.warn('user %s NOT authenticated', user.username)
338
338
339 p = ''
339 p = ''
340 if request.environ.get('SCRIPT_NAME') != '/':
340 if request.environ.get('SCRIPT_NAME') != '/':
341 p += request.environ.get('SCRIPT_NAME')
341 p += request.environ.get('SCRIPT_NAME')
342
342
343 p += request.environ.get('PATH_INFO')
343 p += request.environ.get('PATH_INFO')
344 if request.environ.get('QUERY_STRING'):
344 if request.environ.get('QUERY_STRING'):
345 p += '?' + request.environ.get('QUERY_STRING')
345 p += '?' + request.environ.get('QUERY_STRING')
346
346
347 log.debug('redirecting to login page with %s', p)
347 log.debug('redirecting to login page with %s', p)
348 return redirect(url('login_home', came_from=p))
348 return redirect(url('login_home', came_from=p))
349
349
350 class NotAnonymous(object):
350 class NotAnonymous(object):
351 """Must be logged in to execute this function else
351 """Must be logged in to execute this function else
352 redirect to login page"""
352 redirect to login page"""
353
353
354 def __call__(self, func):
354 def __call__(self, func):
355 return decorator(self.__wrapper, func)
355 return decorator(self.__wrapper, func)
356
356
357 def __wrapper(self, func, *fargs, **fkwargs):
357 def __wrapper(self, func, *fargs, **fkwargs):
358 cls = fargs[0]
358 cls = fargs[0]
359 self.user = cls.rhodecode_user
359 self.user = cls.rhodecode_user
360
360
361 log.debug('Checking if user is not anonymous @%s', cls)
361 log.debug('Checking if user is not anonymous @%s', cls)
362
362
363 anonymous = self.user.username == 'default'
363 anonymous = self.user.username == 'default'
364
364
365 if anonymous:
365 if anonymous:
366 p = ''
366 p = ''
367 if request.environ.get('SCRIPT_NAME') != '/':
367 if request.environ.get('SCRIPT_NAME') != '/':
368 p += request.environ.get('SCRIPT_NAME')
368 p += request.environ.get('SCRIPT_NAME')
369
369
370 p += request.environ.get('PATH_INFO')
370 p += request.environ.get('PATH_INFO')
371 if request.environ.get('QUERY_STRING'):
371 if request.environ.get('QUERY_STRING'):
372 p += '?' + request.environ.get('QUERY_STRING')
372 p += '?' + request.environ.get('QUERY_STRING')
373
373
374 import rhodecode.lib.helpers as h
374 import rhodecode.lib.helpers as h
375 h.flash(_('You need to be a registered user to perform this action'),
375 h.flash(_('You need to be a registered user to perform this action'),
376 category='warning')
376 category='warning')
377 return redirect(url('login_home', came_from=p))
377 return redirect(url('login_home', came_from=p))
378 else:
378 else:
379 return func(*fargs, **fkwargs)
379 return func(*fargs, **fkwargs)
380
380
381 class PermsDecorator(object):
381 class PermsDecorator(object):
382 """Base class for controller decorators"""
382 """Base class for controller decorators"""
383
383
384 def __init__(self, *required_perms):
384 def __init__(self, *required_perms):
385 available_perms = config['available_permissions']
385 available_perms = config['available_permissions']
386 for perm in required_perms:
386 for perm in required_perms:
387 if perm not in available_perms:
387 if perm not in available_perms:
388 raise Exception("'%s' permission is not defined" % perm)
388 raise Exception("'%s' permission is not defined" % perm)
389 self.required_perms = set(required_perms)
389 self.required_perms = set(required_perms)
390 self.user_perms = None
390 self.user_perms = None
391
391
392 def __call__(self, func):
392 def __call__(self, func):
393 return decorator(self.__wrapper, func)
393 return decorator(self.__wrapper, func)
394
394
395
395
396 def __wrapper(self, func, *fargs, **fkwargs):
396 def __wrapper(self, func, *fargs, **fkwargs):
397 cls = fargs[0]
397 cls = fargs[0]
398 self.user = cls.rhodecode_user
398 self.user = cls.rhodecode_user
399 self.user_perms = self.user.permissions
399 self.user_perms = self.user.permissions
400 log.debug('checking %s permissions %s for %s %s',
400 log.debug('checking %s permissions %s for %s %s',
401 self.__class__.__name__, self.required_perms, cls,
401 self.__class__.__name__, self.required_perms, cls,
402 self.user)
402 self.user)
403
403
404 if self.check_permissions():
404 if self.check_permissions():
405 log.debug('Permission granted for %s %s', cls, self.user)
405 log.debug('Permission granted for %s %s', cls, self.user)
406 return func(*fargs, **fkwargs)
406 return func(*fargs, **fkwargs)
407
407
408 else:
408 else:
409 log.warning('Permission denied for %s %s', cls, self.user)
409 log.warning('Permission denied for %s %s', cls, self.user)
410 #redirect with forbidden ret code
410 #redirect with forbidden ret code
411 return abort(403)
411 return abort(403)
412
412
413
413
414
414
415 def check_permissions(self):
415 def check_permissions(self):
416 """Dummy function for overriding"""
416 """Dummy function for overriding"""
417 raise Exception('You have to write this function in child class')
417 raise Exception('You have to write this function in child class')
418
418
419 class HasPermissionAllDecorator(PermsDecorator):
419 class HasPermissionAllDecorator(PermsDecorator):
420 """Checks for access permission for all given predicates. All of them
420 """Checks for access permission for all given predicates. All of them
421 have to be meet in order to fulfill the request
421 have to be meet in order to fulfill the request
422 """
422 """
423
423
424 def check_permissions(self):
424 def check_permissions(self):
425 if self.required_perms.issubset(self.user_perms.get('global')):
425 if self.required_perms.issubset(self.user_perms.get('global')):
426 return True
426 return True
427 return False
427 return False
428
428
429
429
430 class HasPermissionAnyDecorator(PermsDecorator):
430 class HasPermissionAnyDecorator(PermsDecorator):
431 """Checks for access permission for any of given predicates. In order to
431 """Checks for access permission for any of given predicates. In order to
432 fulfill the request any of predicates must be meet
432 fulfill the request any of predicates must be meet
433 """
433 """
434
434
435 def check_permissions(self):
435 def check_permissions(self):
436 if self.required_perms.intersection(self.user_perms.get('global')):
436 if self.required_perms.intersection(self.user_perms.get('global')):
437 return True
437 return True
438 return False
438 return False
439
439
440 class HasRepoPermissionAllDecorator(PermsDecorator):
440 class HasRepoPermissionAllDecorator(PermsDecorator):
441 """Checks for access permission for all given predicates for specific
441 """Checks for access permission for all given predicates for specific
442 repository. All of them have to be meet in order to fulfill the request
442 repository. All of them have to be meet in order to fulfill the request
443 """
443 """
444
444
445 def check_permissions(self):
445 def check_permissions(self):
446 repo_name = get_repo_slug(request)
446 repo_name = get_repo_slug(request)
447 try:
447 try:
448 user_perms = set([self.user_perms['repositories'][repo_name]])
448 user_perms = set([self.user_perms['repositories'][repo_name]])
449 except KeyError:
449 except KeyError:
450 return False
450 return False
451 if self.required_perms.issubset(user_perms):
451 if self.required_perms.issubset(user_perms):
452 return True
452 return True
453 return False
453 return False
454
454
455
455
456 class HasRepoPermissionAnyDecorator(PermsDecorator):
456 class HasRepoPermissionAnyDecorator(PermsDecorator):
457 """Checks for access permission for any of given predicates for specific
457 """Checks for access permission for any of given predicates for specific
458 repository. In order to fulfill the request any of predicates must be meet
458 repository. In order to fulfill the request any of predicates must be meet
459 """
459 """
460
460
461 def check_permissions(self):
461 def check_permissions(self):
462 repo_name = get_repo_slug(request)
462 repo_name = get_repo_slug(request)
463
463
464 try:
464 try:
465 user_perms = set([self.user_perms['repositories'][repo_name]])
465 user_perms = set([self.user_perms['repositories'][repo_name]])
466 except KeyError:
466 except KeyError:
467 return False
467 return False
468 if self.required_perms.intersection(user_perms):
468 if self.required_perms.intersection(user_perms):
469 return True
469 return True
470 return False
470 return False
471 #===============================================================================
471 #===============================================================================
472 # CHECK FUNCTIONS
472 # CHECK FUNCTIONS
473 #===============================================================================
473 #===============================================================================
474
474
475 class PermsFunction(object):
475 class PermsFunction(object):
476 """Base function for other check functions"""
476 """Base function for other check functions"""
477
477
478 def __init__(self, *perms):
478 def __init__(self, *perms):
479 available_perms = config['available_permissions']
479 available_perms = config['available_permissions']
480
480
481 for perm in perms:
481 for perm in perms:
482 if perm not in available_perms:
482 if perm not in available_perms:
483 raise Exception("'%s' permission in not defined" % perm)
483 raise Exception("'%s' permission in not defined" % perm)
484 self.required_perms = set(perms)
484 self.required_perms = set(perms)
485 self.user_perms = None
485 self.user_perms = None
486 self.granted_for = ''
486 self.granted_for = ''
487 self.repo_name = None
487 self.repo_name = None
488
488
489 def __call__(self, check_Location=''):
489 def __call__(self, check_Location=''):
490 user = session.get('rhodecode_user', False)
490 user = session.get('rhodecode_user', False)
491 if not user:
491 if not user:
492 return False
492 return False
493 self.user_perms = user.permissions
493 self.user_perms = user.permissions
494 self.granted_for = user
494 self.granted_for = user
495 log.debug('checking %s %s %s', self.__class__.__name__,
495 log.debug('checking %s %s %s', self.__class__.__name__,
496 self.required_perms, user)
496 self.required_perms, user)
497
497
498 if self.check_permissions():
498 if self.check_permissions():
499 log.debug('Permission granted %s @ %s', self.granted_for,
499 log.debug('Permission granted %s @ %s', self.granted_for,
500 check_Location or 'unspecified location')
500 check_Location or 'unspecified location')
501 return True
501 return True
502
502
503 else:
503 else:
504 log.warning('Permission denied for %s @ %s', self.granted_for,
504 log.warning('Permission denied for %s @ %s', self.granted_for,
505 check_Location or 'unspecified location')
505 check_Location or 'unspecified location')
506 return False
506 return False
507
507
508 def check_permissions(self):
508 def check_permissions(self):
509 """Dummy function for overriding"""
509 """Dummy function for overriding"""
510 raise Exception('You have to write this function in child class')
510 raise Exception('You have to write this function in child class')
511
511
512 class HasPermissionAll(PermsFunction):
512 class HasPermissionAll(PermsFunction):
513 def check_permissions(self):
513 def check_permissions(self):
514 if self.required_perms.issubset(self.user_perms.get('global')):
514 if self.required_perms.issubset(self.user_perms.get('global')):
515 return True
515 return True
516 return False
516 return False
517
517
518 class HasPermissionAny(PermsFunction):
518 class HasPermissionAny(PermsFunction):
519 def check_permissions(self):
519 def check_permissions(self):
520 if self.required_perms.intersection(self.user_perms.get('global')):
520 if self.required_perms.intersection(self.user_perms.get('global')):
521 return True
521 return True
522 return False
522 return False
523
523
524 class HasRepoPermissionAll(PermsFunction):
524 class HasRepoPermissionAll(PermsFunction):
525
525
526 def __call__(self, repo_name=None, check_Location=''):
526 def __call__(self, repo_name=None, check_Location=''):
527 self.repo_name = repo_name
527 self.repo_name = repo_name
528 return super(HasRepoPermissionAll, self).__call__(check_Location)
528 return super(HasRepoPermissionAll, self).__call__(check_Location)
529
529
530 def check_permissions(self):
530 def check_permissions(self):
531 if not self.repo_name:
531 if not self.repo_name:
532 self.repo_name = get_repo_slug(request)
532 self.repo_name = get_repo_slug(request)
533
533
534 try:
534 try:
535 self.user_perms = set([self.user_perms['repositories']\
535 self.user_perms = set([self.user_perms['repositories']\
536 [self.repo_name]])
536 [self.repo_name]])
537 except KeyError:
537 except KeyError:
538 return False
538 return False
539 self.granted_for = self.repo_name
539 self.granted_for = self.repo_name
540 if self.required_perms.issubset(self.user_perms):
540 if self.required_perms.issubset(self.user_perms):
541 return True
541 return True
542 return False
542 return False
543
543
544 class HasRepoPermissionAny(PermsFunction):
544 class HasRepoPermissionAny(PermsFunction):
545
545
546 def __call__(self, repo_name=None, check_Location=''):
546 def __call__(self, repo_name=None, check_Location=''):
547 self.repo_name = repo_name
547 self.repo_name = repo_name
548 return super(HasRepoPermissionAny, self).__call__(check_Location)
548 return super(HasRepoPermissionAny, self).__call__(check_Location)
549
549
550 def check_permissions(self):
550 def check_permissions(self):
551 if not self.repo_name:
551 if not self.repo_name:
552 self.repo_name = get_repo_slug(request)
552 self.repo_name = get_repo_slug(request)
553
553
554 try:
554 try:
555 self.user_perms = set([self.user_perms['repositories']\
555 self.user_perms = set([self.user_perms['repositories']\
556 [self.repo_name]])
556 [self.repo_name]])
557 except KeyError:
557 except KeyError:
558 return False
558 return False
559 self.granted_for = self.repo_name
559 self.granted_for = self.repo_name
560 if self.required_perms.intersection(self.user_perms):
560 if self.required_perms.intersection(self.user_perms):
561 return True
561 return True
562 return False
562 return False
563
563
564 #===============================================================================
564 #===============================================================================
565 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
565 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
566 #===============================================================================
566 #===============================================================================
567
567
568 class HasPermissionAnyMiddleware(object):
568 class HasPermissionAnyMiddleware(object):
569 def __init__(self, *perms):
569 def __init__(self, *perms):
570 self.required_perms = set(perms)
570 self.required_perms = set(perms)
571
571
572 def __call__(self, user, repo_name):
572 def __call__(self, user, repo_name):
573 usr = AuthUser(user.user_id)
573 usr = AuthUser(user.user_id)
574 try:
574 try:
575 self.user_perms = set([usr.permissions['repositories'][repo_name]])
575 self.user_perms = set([usr.permissions['repositories'][repo_name]])
576 except:
576 except:
577 self.user_perms = set()
577 self.user_perms = set()
578 self.granted_for = ''
578 self.granted_for = ''
579 self.username = user.username
579 self.username = user.username
580 self.repo_name = repo_name
580 self.repo_name = repo_name
581 return self.check_permissions()
581 return self.check_permissions()
582
582
583 def check_permissions(self):
583 def check_permissions(self):
584 log.debug('checking mercurial protocol '
584 log.debug('checking mercurial protocol '
585 'permissions %s for user:%s repository:%s', self.user_perms,
585 'permissions %s for user:%s repository:%s', self.user_perms,
586 self.username, self.repo_name)
586 self.username, self.repo_name)
587 if self.required_perms.intersection(self.user_perms):
587 if self.required_perms.intersection(self.user_perms):
588 log.debug('permission granted')
588 log.debug('permission granted')
589 return True
589 return True
590 log.debug('permission denied')
590 log.debug('permission denied')
591 return False
591 return False
@@ -1,343 +1,344 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.user
3 rhodecode.model.user
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 users model for RhodeCode
6 users model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import logging
28 import logging
29 import traceback
29 import traceback
30
30
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 from rhodecode.model import BaseModel
33 from rhodecode.model import BaseModel
34 from rhodecode.model.caching_query import FromCache
34 from rhodecode.model.caching_query import FromCache
35 from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
35 from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
36 UserToPerm, UsersGroupToPerm, UsersGroupMember
36 UserToPerm, UsersGroupToPerm, UsersGroupMember
37 from rhodecode.lib.exceptions import DefaultUserException, UserOwnsReposException
37 from rhodecode.lib.exceptions import DefaultUserException, UserOwnsReposException
38
38
39 from sqlalchemy.exc import DatabaseError
39 from sqlalchemy.exc import DatabaseError
40 from rhodecode.lib import generate_api_key
40 from rhodecode.lib import generate_api_key
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44 PERM_WEIGHTS = {'repository.none':0,
44 PERM_WEIGHTS = {'repository.none':0,
45 'repository.read':1,
45 'repository.read':1,
46 'repository.write':3,
46 'repository.write':3,
47 'repository.admin':3}
47 'repository.admin':3}
48
48
49 class UserModel(BaseModel):
49 class UserModel(BaseModel):
50
50
51 def get(self, user_id, cache=False):
51 def get(self, user_id, cache=False):
52 user = self.sa.query(User)
52 user = self.sa.query(User)
53 if cache:
53 if cache:
54 user = user.options(FromCache("sql_cache_short",
54 user = user.options(FromCache("sql_cache_short",
55 "get_user_%s" % user_id))
55 "get_user_%s" % user_id))
56 return user.get(user_id)
56 return user.get(user_id)
57
57
58
58
59 def get_by_username(self, username, cache=False, case_insensitive=False):
59 def get_by_username(self, username, cache=False, case_insensitive=False):
60
60
61 if case_insensitive:
61 if case_insensitive:
62 user = self.sa.query(User).filter(User.username.ilike(username))
62 user = self.sa.query(User).filter(User.username.ilike(username))
63 else:
63 else:
64 user = self.sa.query(User)\
64 user = self.sa.query(User)\
65 .filter(User.username == username)
65 .filter(User.username == username)
66 if cache:
66 if cache:
67 user = user.options(FromCache("sql_cache_short",
67 user = user.options(FromCache("sql_cache_short",
68 "get_user_%s" % username))
68 "get_user_%s" % username))
69 return user.scalar()
69 return user.scalar()
70
70
71
71
72 def get_by_api_key(self, api_key, cache=False):
72 def get_by_api_key(self, api_key, cache=False):
73
73
74 user = self.sa.query(User)\
74 user = self.sa.query(User)\
75 .filter(User.api_key == api_key)
75 .filter(User.api_key == api_key)
76 if cache:
76 if cache:
77 user = user.options(FromCache("sql_cache_short",
77 user = user.options(FromCache("sql_cache_short",
78 "get_user_%s" % api_key))
78 "get_user_%s" % api_key))
79 return user.scalar()
79 return user.scalar()
80
80
81 def create(self, form_data):
81 def create(self, form_data):
82 try:
82 try:
83 new_user = User()
83 new_user = User()
84 for k, v in form_data.items():
84 for k, v in form_data.items():
85 setattr(new_user, k, v)
85 setattr(new_user, k, v)
86
86
87 new_user.api_key = generate_api_key(form_data['username'])
87 new_user.api_key = generate_api_key(form_data['username'])
88 self.sa.add(new_user)
88 self.sa.add(new_user)
89 self.sa.commit()
89 self.sa.commit()
90 except:
90 except:
91 log.error(traceback.format_exc())
91 log.error(traceback.format_exc())
92 self.sa.rollback()
92 self.sa.rollback()
93 raise
93 raise
94
94
95 def create_ldap(self, username, password, user_dn, attrs):
95 def create_ldap(self, username, password, user_dn, attrs):
96 """
96 """
97 Checks if user is in database, if not creates this user marked
97 Checks if user is in database, if not creates this user marked
98 as ldap user
98 as ldap user
99 :param username:
99 :param username:
100 :param password:
100 :param password:
101 :param user_dn:
101 :param user_dn:
102 :param attrs:
102 :param attrs:
103 """
103 """
104 from rhodecode.lib.auth import get_crypt_password
104 from rhodecode.lib.auth import get_crypt_password
105 log.debug('Checking for such ldap account in RhodeCode database')
105 log.debug('Checking for such ldap account in RhodeCode database')
106 if self.get_by_username(username, case_insensitive=True) is None:
106 if self.get_by_username(username, case_insensitive=True) is None:
107 try:
107 try:
108 new_user = User()
108 new_user = User()
109 new_user.username = username.lower() # add ldap account always lowercase
109 new_user.username = username.lower() # add ldap account always lowercase
110 new_user.password = get_crypt_password(password)
110 new_user.password = get_crypt_password(password)
111 new_user.api_key = generate_api_key(username)
111 new_user.api_key = generate_api_key(username)
112 new_user.email = attrs['email']
112 new_user.email = attrs['email']
113 new_user.active = True
113 new_user.active = True
114 new_user.ldap_dn = user_dn
114 new_user.ldap_dn = user_dn
115 new_user.name = attrs['name']
115 new_user.name = attrs['name']
116 new_user.lastname = attrs['lastname']
116 new_user.lastname = attrs['lastname']
117
117
118
118
119 self.sa.add(new_user)
119 self.sa.add(new_user)
120 self.sa.commit()
120 self.sa.commit()
121 return True
121 return True
122 except (DatabaseError,):
122 except (DatabaseError,):
123 log.error(traceback.format_exc())
123 log.error(traceback.format_exc())
124 self.sa.rollback()
124 self.sa.rollback()
125 raise
125 raise
126 log.debug('this %s user exists skipping creation of ldap account',
126 log.debug('this %s user exists skipping creation of ldap account',
127 username)
127 username)
128 return False
128 return False
129
129
130 def create_registration(self, form_data):
130 def create_registration(self, form_data):
131 from rhodecode.lib.celerylib import tasks, run_task
131 from rhodecode.lib.celerylib import tasks, run_task
132 try:
132 try:
133 new_user = User()
133 new_user = User()
134 for k, v in form_data.items():
134 for k, v in form_data.items():
135 if k != 'admin':
135 if k != 'admin':
136 setattr(new_user, k, v)
136 setattr(new_user, k, v)
137
137
138 self.sa.add(new_user)
138 self.sa.add(new_user)
139 self.sa.commit()
139 self.sa.commit()
140 body = ('New user registration\n'
140 body = ('New user registration\n'
141 'username: %s\n'
141 'username: %s\n'
142 'email: %s\n')
142 'email: %s\n')
143 body = body % (form_data['username'], form_data['email'])
143 body = body % (form_data['username'], form_data['email'])
144
144
145 run_task(tasks.send_email, None,
145 run_task(tasks.send_email, None,
146 _('[RhodeCode] New User registration'),
146 _('[RhodeCode] New User registration'),
147 body)
147 body)
148 except:
148 except:
149 log.error(traceback.format_exc())
149 log.error(traceback.format_exc())
150 self.sa.rollback()
150 self.sa.rollback()
151 raise
151 raise
152
152
153 def update(self, user_id, form_data):
153 def update(self, user_id, form_data):
154 try:
154 try:
155 user = self.get(user_id, cache=False)
155 user = self.get(user_id, cache=False)
156 if user.username == 'default':
156 if user.username == 'default':
157 raise DefaultUserException(
157 raise DefaultUserException(
158 _("You can't Edit this user since it's"
158 _("You can't Edit this user since it's"
159 " crucial for entire application"))
159 " crucial for entire application"))
160
160
161 for k, v in form_data.items():
161 for k, v in form_data.items():
162 if k == 'new_password' and v != '':
162 if k == 'new_password' and v != '':
163 user.password = v
163 user.password = v
164 user.api_key = generate_api_key(user.username)
164 user.api_key = generate_api_key(user.username)
165 else:
165 else:
166 setattr(user, k, v)
166 setattr(user, k, v)
167
167
168 self.sa.add(user)
168 self.sa.add(user)
169 self.sa.commit()
169 self.sa.commit()
170 except:
170 except:
171 log.error(traceback.format_exc())
171 log.error(traceback.format_exc())
172 self.sa.rollback()
172 self.sa.rollback()
173 raise
173 raise
174
174
175 def update_my_account(self, user_id, form_data):
175 def update_my_account(self, user_id, form_data):
176 try:
176 try:
177 user = self.get(user_id, cache=False)
177 user = self.get(user_id, cache=False)
178 if user.username == 'default':
178 if user.username == 'default':
179 raise DefaultUserException(
179 raise DefaultUserException(
180 _("You can't Edit this user since it's"
180 _("You can't Edit this user since it's"
181 " crucial for entire application"))
181 " crucial for entire application"))
182 for k, v in form_data.items():
182 for k, v in form_data.items():
183 if k == 'new_password' and v != '':
183 if k == 'new_password' and v != '':
184 user.password = v
184 user.password = v
185 user.api_key = generate_api_key(user.username)
185 user.api_key = generate_api_key(user.username)
186 else:
186 else:
187 if k not in ['admin', 'active']:
187 if k not in ['admin', 'active']:
188 setattr(user, k, v)
188 setattr(user, k, v)
189
189
190 self.sa.add(user)
190 self.sa.add(user)
191 self.sa.commit()
191 self.sa.commit()
192 except:
192 except:
193 log.error(traceback.format_exc())
193 log.error(traceback.format_exc())
194 self.sa.rollback()
194 self.sa.rollback()
195 raise
195 raise
196
196
197 def delete(self, user_id):
197 def delete(self, user_id):
198 try:
198 try:
199 user = self.get(user_id, cache=False)
199 user = self.get(user_id, cache=False)
200 if user.username == 'default':
200 if user.username == 'default':
201 raise DefaultUserException(
201 raise DefaultUserException(
202 _("You can't remove this user since it's"
202 _("You can't remove this user since it's"
203 " crucial for entire application"))
203 " crucial for entire application"))
204 if user.repositories:
204 if user.repositories:
205 raise UserOwnsReposException(_('This user still owns %s '
205 raise UserOwnsReposException(_('This user still owns %s '
206 'repositories and cannot be '
206 'repositories and cannot be '
207 'removed. Switch owners or '
207 'removed. Switch owners or '
208 'remove those repositories') \
208 'remove those repositories') \
209 % user.repositories)
209 % user.repositories)
210 self.sa.delete(user)
210 self.sa.delete(user)
211 self.sa.commit()
211 self.sa.commit()
212 except:
212 except:
213 log.error(traceback.format_exc())
213 log.error(traceback.format_exc())
214 self.sa.rollback()
214 self.sa.rollback()
215 raise
215 raise
216
216
217 def reset_password(self, data):
217 def reset_password(self, data):
218 from rhodecode.lib.celerylib import tasks, run_task
218 from rhodecode.lib.celerylib import tasks, run_task
219 run_task(tasks.reset_user_password, data['email'])
219 run_task(tasks.reset_user_password, data['email'])
220
220
221
221
222 def fill_data(self, auth_user, user_id=None, api_key=None):
222 def fill_data(self, auth_user, user_id=None, api_key=None):
223 """
223 """
224 Fetches auth_user by user_id,or api_key if present.
224 Fetches auth_user by user_id,or api_key if present.
225 Fills auth_user attributes with those taken from database.
225 Fills auth_user attributes with those taken from database.
226 Additionally set's is_authenitated if lookup fails
226 Additionally set's is_authenitated if lookup fails
227 present in database
227 present in database
228
228
229 :param auth_user: instance of user to set attributes
229 :param auth_user: instance of user to set attributes
230 :param user_id: user id to fetch by
230 :param user_id: user id to fetch by
231 :param api_key: api key to fetch by
231 :param api_key: api key to fetch by
232 """
232 """
233 if not user_id and not not api_key:
233 if user_id is None and api_key is None:
234 raise Exception('You need to pass user_id or api_key')
234 raise Exception('You need to pass user_id or api_key')
235
235
236 try:
236 try:
237 if api_key:
237 if api_key:
238 dbuser = self.get_by_api_key(api_key)
238 dbuser = self.get_by_api_key(api_key)
239 else:
239 else:
240 dbuser = self.get(user_id)
240 dbuser = self.get(user_id)
241
241
242 log.debug('filling %s data', dbuser)
242 if dbuser is not None:
243 for k, v in dbuser.get_dict().items():
243 log.debug('filling %s data', dbuser)
244 setattr(auth_user, k, v)
244 for k, v in dbuser.get_dict().items():
245 setattr(auth_user, k, v)
245
246
246 except:
247 except:
247 log.error(traceback.format_exc())
248 log.error(traceback.format_exc())
248 auth_user.is_authenticated = False
249 auth_user.is_authenticated = False
249
250
250 return auth_user
251 return auth_user
251
252
252
253
253 def fill_perms(self, user):
254 def fill_perms(self, user):
254 """Fills user permission attribute with permissions taken from database
255 """Fills user permission attribute with permissions taken from database
255 works for permissions given for repositories, and for permissions that
256 works for permissions given for repositories, and for permissions that
256 as part of beeing group member
257 as part of beeing group member
257
258
258 :param user: user instance to fill his perms
259 :param user: user instance to fill his perms
259 """
260 """
260
261
261 user.permissions['repositories'] = {}
262 user.permissions['repositories'] = {}
262 user.permissions['global'] = set()
263 user.permissions['global'] = set()
263
264
264 #===========================================================================
265 #===========================================================================
265 # fetch default permissions
266 # fetch default permissions
266 #===========================================================================
267 #===========================================================================
267 default_user = self.get_by_username('default', cache=True)
268 default_user = self.get_by_username('default', cache=True)
268
269
269 default_perms = self.sa.query(RepoToPerm, Repository, Permission)\
270 default_perms = self.sa.query(RepoToPerm, Repository, Permission)\
270 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
271 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
271 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
272 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
272 .filter(RepoToPerm.user == default_user).all()
273 .filter(RepoToPerm.user == default_user).all()
273
274
274 if user.is_admin:
275 if user.is_admin:
275 #=======================================================================
276 #=======================================================================
276 # #admin have all default rights set to admin
277 # #admin have all default rights set to admin
277 #=======================================================================
278 #=======================================================================
278 user.permissions['global'].add('hg.admin')
279 user.permissions['global'].add('hg.admin')
279
280
280 for perm in default_perms:
281 for perm in default_perms:
281 p = 'repository.admin'
282 p = 'repository.admin'
282 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
283 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
283
284
284 else:
285 else:
285 #=======================================================================
286 #=======================================================================
286 # set default permissions
287 # set default permissions
287 #=======================================================================
288 #=======================================================================
288
289
289 #default global
290 #default global
290 default_global_perms = self.sa.query(UserToPerm)\
291 default_global_perms = self.sa.query(UserToPerm)\
291 .filter(UserToPerm.user == self.sa.query(User)\
292 .filter(UserToPerm.user == self.sa.query(User)\
292 .filter(User.username == 'default').one())
293 .filter(User.username == 'default').one())
293
294
294 for perm in default_global_perms:
295 for perm in default_global_perms:
295 user.permissions['global'].add(perm.permission.permission_name)
296 user.permissions['global'].add(perm.permission.permission_name)
296
297
297 #default for repositories
298 #default for repositories
298 for perm in default_perms:
299 for perm in default_perms:
299 if perm.Repository.private and not perm.Repository.user_id == user.user_id:
300 if perm.Repository.private and not perm.Repository.user_id == user.user_id:
300 #diself.sable defaults for private repos,
301 #diself.sable defaults for private repos,
301 p = 'repository.none'
302 p = 'repository.none'
302 elif perm.Repository.user_id == user.user_id:
303 elif perm.Repository.user_id == user.user_id:
303 #set admin if owner
304 #set admin if owner
304 p = 'repository.admin'
305 p = 'repository.admin'
305 else:
306 else:
306 p = perm.Permission.permission_name
307 p = perm.Permission.permission_name
307
308
308 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
309 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
309
310
310 #=======================================================================
311 #=======================================================================
311 # overwrite default with user permissions if any
312 # overwrite default with user permissions if any
312 #=======================================================================
313 #=======================================================================
313 user_perms = self.sa.query(RepoToPerm, Permission, Repository)\
314 user_perms = self.sa.query(RepoToPerm, Permission, Repository)\
314 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
315 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
315 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
316 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
316 .filter(RepoToPerm.user_id == user.user_id).all()
317 .filter(RepoToPerm.user_id == user.user_id).all()
317
318
318 for perm in user_perms:
319 for perm in user_perms:
319 if perm.Repository.user_id == user.user_id:#set admin if owner
320 if perm.Repository.user_id == user.user_id:#set admin if owner
320 p = 'repository.admin'
321 p = 'repository.admin'
321 else:
322 else:
322 p = perm.Permission.permission_name
323 p = perm.Permission.permission_name
323 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
324 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
324
325
325
326
326 #=======================================================================
327 #=======================================================================
327 # check if user is part of groups for this repository and fill in
328 # check if user is part of groups for this repository and fill in
328 # (or replace with higher) permissions
329 # (or replace with higher) permissions
329 #=======================================================================
330 #=======================================================================
330 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm, Permission, Repository,)\
331 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm, Permission, Repository,)\
331 .join((Repository, UsersGroupToPerm.repository_id == Repository.repo_id))\
332 .join((Repository, UsersGroupToPerm.repository_id == Repository.repo_id))\
332 .join((Permission, UsersGroupToPerm.permission_id == Permission.permission_id))\
333 .join((Permission, UsersGroupToPerm.permission_id == Permission.permission_id))\
333 .join((UsersGroupMember, UsersGroupToPerm.users_group_id == UsersGroupMember.users_group_id))\
334 .join((UsersGroupMember, UsersGroupToPerm.users_group_id == UsersGroupMember.users_group_id))\
334 .filter(UsersGroupMember.user_id == user.user_id).all()
335 .filter(UsersGroupMember.user_id == user.user_id).all()
335
336
336 for perm in user_perms_from_users_groups:
337 for perm in user_perms_from_users_groups:
337 p = perm.Permission.permission_name
338 p = perm.Permission.permission_name
338 cur_perm = user.permissions['repositories'][perm.UsersGroupToPerm.repository.repo_name]
339 cur_perm = user.permissions['repositories'][perm.UsersGroupToPerm.repository.repo_name]
339 #overwrite permission only if it's greater than permission given from other sources
340 #overwrite permission only if it's greater than permission given from other sources
340 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
341 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
341 user.permissions['repositories'][perm.UsersGroupToPerm.repository.repo_name] = p
342 user.permissions['repositories'][perm.UsersGroupToPerm.repository.repo_name] = p
342
343
343 return user
344 return user
General Comments 0
You need to be logged in to leave comments. Login now