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