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