##// END OF EJS Templates
spelling: more consistent casing of 'IP'
Mads Kiilerich -
r5125:2aeaf636 default
parent child Browse files
Show More
@@ -1,291 +1,291 b''
1 1 .. _installation_win_old:
2 2
3 3
4 4 Installation and upgrade on Windows (XP/Vista/Server 2003/Server 2008)
5 5 ======================================================================
6 6
7 7 First time install
8 8 ::::::::::::::::::
9 9
10 10 Target OS: Windows XP SP3 32bit English (Clean installation)
11 11 + All Windows Updates until 24-may-2012
12 12
13 13 .. note::
14 14
15 15 This installation is for 32bit systems, for 64bit windows you might need
16 16 to download proper 64bit versions of the different packages(Windows Installer, Win32py extensions)
17 17 plus some extra tweaks.
18 18 These extra steps haven been marked as "64bit".
19 19 Tested on Windows Server 2008 R2 SP1, 9-feb-2013.
20 20 If you run into any 64bit related problems, please check these pages:
21 21 - http://blog.victorjabur.com/2011/06/05/compiling-python-2-7-modules-on-windows-32-and-64-using-msvc-2008-express/
22 22 - http://bugs.python.org/issue7511
23 23
24 24 Step1 - Install Visual Studio 2008 Express
25 25 ------------------------------------------
26 26
27 27
28 28 Optional: You can also install MinGW, but VS2008 installation is easier.
29 29
30 30 Download "Visual C++ 2008 Express Edition with SP1" from:
31 31 http://download.microsoft.com/download/E/8/E/E8EEB394-7F42-4963-A2D8-29559B738298/VS2008ExpressWithSP1ENUX1504728.iso
32 32 (if not found or relocated, google for "visual studio 2008 express" for updated link. This link was taken from http://stackoverflow.com/questions/15318560/visual-c-2008-express-download-link-dead)
33 33
34 34 You can also download full ISO file for offline installation, just
35 35 choose "All - Offline Install ISO image file" in the previous page and
36 36 choose "Visual C++ 2008 Express" when installing.
37 37
38 38 .. note::
39 39
40 40 Using other versions of Visual Studio will lead to random crashes.
41 41 You must use Visual Studio 2008!"
42 42
43 43 .. note::
44 44
45 45 Silverlight Runtime and SQL Server 2008 Express Edition are not
46 46 required, you can uncheck them
47 47
48 48 .. note::
49 49
50 50 64bit: You also need to install the Microsoft Windows SDK for .NET 3.5 SP1 (.NET 4.0 won't work).
51 51 Download from: http://www.microsoft.com/en-us/download/details.aspx?id=3138
52 52
53 53 .. note::
54 54
55 55 64bit: You also need to copy and rename a .bat file to make the Visual C++ compiler work.
56 56 I am not sure why this is not necessary for 32bit.
57 57 Copy C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\vcvars64.bat to C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\amd64\vcvarsamd64.bat
58 58
59 59
60 60 Step2 - Install Python
61 61 ----------------------
62 62
63 63 Install Python 2.x.y (x = 6 or 7) x86 version (32bit). DO NOT USE A 3.x version.
64 64 Download Python 2.x.y from:
65 65 http://www.python.org/download/
66 66
67 67 Choose "Windows Installer" (32bit version) not "Windows X86-64
68 68 Installer". While writing this guide, the latest version was v2.7.3.
69 69 Remember the specific major and minor version installed, because it will
70 70 be needed in the next step. In this case, it is "2.7".
71 71
72 72 .. note::
73 73
74 74 64bit: Just download and install the 64bit version of python.
75 75
76 76 Step3 - Install Win32py extensions
77 77 ----------------------------------
78 78
79 79 Download pywin32 from:
80 80 http://sourceforge.net/projects/pywin32/files/
81 81
82 82 - Click on "pywin32" folder
83 83 - Click on the first folder (in this case, Build 217, maybe newer when you try)
84 84 - Choose the file ending with ".win32-py2.x.exe" -> x being the minor
85 85 version of Python you installed (in this case, 7)
86 86 When writing this guide, the file was:
87 87 http://sourceforge.net/projects/pywin32/files/pywin32/Build%20217/pywin32-217.win32-py2.7.exe/download
88 88
89 89 .. note::
90 90
91 91 64bit: Download and install the 64bit version.
92 92 At the time of writing you can find this at:
93 93 http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win-amd64-py2.7.exe/download
94 94
95 95 Step4 - Python BIN
96 96 ------------------
97 97
98 98 Add Python BIN folder to the path
99 99
100 100 You have to add the Python folder to the path, you can do it manually
101 101 (editing "PATH" environment variable) or using Windows Support Tools
102 102 that came preinstalled in Vista/7 and can be installed in Windows XP.
103 103
104 104 - Using support tools on WINDOWS XP:
105 105 If you use Windows XP you can install them using Windows XP CD and
106 106 navigating to \SUPPORT\TOOLS. There, execute Setup.EXE (not MSI).
107 107 Afterwards, open a CMD and type::
108 108
109 109 SETX PATH "%PATH%;[your-python-path]" -M
110 110
111 111 Close CMD (the path variable will be updated then)
112 112
113 113 - Using support tools on WINDOWS Vista/7:
114 114
115 115 Open a CMD and type::
116 116
117 117 SETX PATH "%PATH%;[your-python-path]" /M
118 118
119 119 Please substitute [your-python-path] with your Python installation path.
120 120 Typically: C:\\Python27
121 121
122 122
123 123 Step5 - Kallithea folder structure
124 124 ----------------------------------
125 125
126 126 Create a Kallithea folder structure
127 127
128 128 This is only a example to install Kallithea, you can of course change
129 129 it. However, this guide will follow the proposed structure, so please
130 130 later adapt the paths if you change them. My recommendation is to use
131 131 folders with NO SPACES. But you can try if you are brave...
132 132
133 133 Create the following folder structure::
134 134
135 135 C:\Kallithea
136 136 C:\Kallithea\Bin
137 137 C:\Kallithea\Env
138 138 C:\Kallithea\Repos
139 139
140 140
141 141 Step6 - Install virtualenv
142 142 ---------------------------
143 143
144 144 Install Virtual Env for Python
145 145
146 146 Navigate to: http://www.virtualenv.org/en/latest/index.html#installation
147 147 Right click on "virtualenv.py" file and choose "Save link as...".
148 148 Download to C:\\Kallithea (or whatever you want)
149 149 (the file is located at
150 150 https://raw.github.com/pypa/virtualenv/master/virtualenv.py)
151 151
152 152 Create a virtual Python environment in C:\\Kallithea\\Env (or similar). To
153 153 do so, open a CMD (Python Path should be included in Step3), navigate
154 154 where you downloaded "virtualenv.py", and write::
155 155
156 156 python virtualenv.py C:\Kallithea\Env
157 157
158 158 (--no-site-packages is now the default behaviour of virtualenv, no need
159 159 to include it)
160 160
161 161
162 162 Step7 - Install Kallithea
163 163 -------------------------
164 164
165 165 Finally, install Kallithea
166 166
167 167 Close previously opened command prompt/s, and open a Visual Studio 2008
168 168 Command Prompt (**IMPORTANT!!**). To do so, go to Start Menu, and then open
169 169 "Microsoft Visual C++ 2008 Express Edition" -> "Visual Studio Tools" ->
170 170 "Visual Studio 2008 Command Prompt"
171 171
172 172 .. note::
173 173
174 174 64bit: For 64bit you need to modify the shortcut that is used to start the
175 175 Visual Studio 2008 Command Prompt. Use right-mouse click to open properties.
176 176
177 177 Change commandline from::
178 178
179 179 %comspec% /k ""C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\vcvarsall.bat"" x86
180 180
181 181 to::
182 182
183 183 %comspec% /k ""C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\vcvarsall.bat"" amd64
184 184
185 185
186 186 In that CMD (loaded with VS2008 PATHs) type::
187 187
188 188 cd C:\Kallithea\Env\Scripts (or similar)
189 189 activate
190 190
191 191 The prompt will change into "(Env) C:\\Kallithea\\Env\\Scripts" or similar
192 192 (depending of your folder structure). Then type::
193 193
194 194 pip install kallithea
195 195
196 196 (long step, please wait until fully complete)
197 197
198 198 Some warnings will appear, don't worry as they are normal.
199 199
200 200
201 201 Step8 - Configuring Kallithea
202 202 -----------------------------
203 203
204 204
205 205 steps taken from http://packages.python.org/Kallithea/setup.html
206 206
207 207 You have to use the same Visual Studio 2008 command prompt as Step7, so
208 208 if you closed it reopen it following the same commands (including the
209 209 "activate" one). When ready, just type::
210 210
211 211 cd C:\Kallithea\Bin
212 212 paster make-config Kallithea production.ini
213 213
214 Then, you must edit production.ini to fit your needs (ip address, ip
214 Then, you must edit production.ini to fit your needs (network address and
215 215 port, mail settings, database, whatever). I recommend using NotePad++
216 216 (free) or similar text editor, as it handles well the EndOfLine
217 217 character differences between Unix and Windows
218 218 (http://notepad-plus-plus.org/)
219 219
220 220 For the sake of simplicity lets run it with the default settings. After
221 221 your edits (if any), in the previous Command Prompt, type::
222 222
223 223 paster setup-db production.ini
224 224
225 225 (this time a NEW database will be installed, you must follow a different
226 226 step to later UPGRADE to a newer Kallithea version)
227 227
228 228 The script will ask you for confirmation about creating a NEW database,
229 229 answer yes (y)
230 230 The script will ask you for repository path, answer C:\\Kallithea\\Repos
231 231 (or similar)
232 232 The script will ask you for admin username and password, answer "admin"
233 233 + "123456" (or whatever you want)
234 234 The script will ask you for admin mail, answer "admin@xxxx.com" (or
235 235 whatever you want)
236 236
237 237 If you make some mistake and the script does not end, don't worry, start
238 238 it again.
239 239
240 240
241 241 Step9 - Running Kallithea
242 242 -------------------------
243 243
244 244
245 245 In the previous command prompt, being in the C:\\Kallithea\\Bin folder,
246 246 just type::
247 247
248 248 paster serve production.ini
249 249
250 250 Open yout web server, and go to http://127.0.0.1:5000
251 251
252 252 It works!! :-)
253 253
254 254 Remark:
255 255 If it does not work first time, just Ctrl-C the CMD process and start it
256 256 again. Don't forget the "http://" in Internet Explorer
257 257
258 258
259 259
260 260 What this Guide does not cover:
261 261
262 262 - Installing Celery
263 263 - Running Kallithea as Windows Service. You can investigate here:
264 264
265 265 - http://pypi.python.org/pypi/wsgisvc
266 266 - http://ryrobes.com/python/running-python-scripts-as-a-windows-service/
267 267 - http://wiki.pylonshq.com/display/pylonscookbook/How+to+run+Pylons+as+a+Windows+service
268 268
269 269 - Using Apache. You can investigate here:
270 270
271 271 - https://groups.google.com/group/rhodecode/msg/c433074e813ffdc4
272 272
273 273
274 274 Upgrading
275 275 :::::::::
276 276
277 277 Stop running Kallithea
278 278 Open a CommandPrompt like in Step7 (VS2008 path + activate) and type::
279 279
280 280 easy_install -U kallithea
281 281 cd \Kallithea\Bin
282 282
283 283 { backup your production.ini file now} ::
284 284
285 285 paster make-config Kallithea production.ini
286 286
287 287 (check changes and update your production.ini accordingly) ::
288 288
289 289 paster upgrade-db production.ini (update database)
290 290
291 291 Full steps in http://packages.python.org/Kallithea/upgrade.html
@@ -1,506 +1,506 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.controllers.admin.users
16 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 17
18 18 Users crud controller for pylons
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: Apr 4, 2010
23 23 :author: marcink
24 24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 25 :license: GPLv3, see LICENSE.md for more details.
26 26 """
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, tmpl_context as c, url, config
34 34 from pylons.controllers.util import redirect
35 35 from pylons.i18n.translation import _
36 36 from sqlalchemy.sql.expression import func
37 37
38 38 import kallithea
39 39 from kallithea.lib.exceptions import DefaultUserException, \
40 40 UserOwnsReposException, UserCreationError
41 41 from kallithea.lib import helpers as h
42 42 from kallithea.lib.auth import LoginRequired, HasPermissionAllDecorator, \
43 43 AuthUser, generate_api_key
44 44 import kallithea.lib.auth_modules.auth_internal
45 45 from kallithea.lib import auth_modules
46 46 from kallithea.lib.base import BaseController, render
47 47 from kallithea.model.api_key import ApiKeyModel
48 48
49 49 from kallithea.model.db import User, UserEmailMap, UserIpMap, UserToPerm
50 50 from kallithea.model.forms import UserForm, CustomDefaultPermissionsForm
51 51 from kallithea.model.user import UserModel
52 52 from kallithea.model.meta import Session
53 53 from kallithea.lib.utils import action_logger
54 54 from kallithea.lib.compat import json
55 55 from kallithea.lib.utils2 import datetime_to_time, safe_int
56 56
57 57 log = logging.getLogger(__name__)
58 58
59 59
60 60 class UsersController(BaseController):
61 61 """REST Controller styled on the Atom Publishing Protocol"""
62 62
63 63 @LoginRequired()
64 64 @HasPermissionAllDecorator('hg.admin')
65 65 def __before__(self):
66 66 super(UsersController, self).__before__()
67 67 c.available_permissions = config['available_permissions']
68 68 c.EXTERN_TYPE_INTERNAL = kallithea.EXTERN_TYPE_INTERNAL
69 69
70 70 def index(self, format='html'):
71 71 """GET /users: All items in the collection"""
72 72 # url('users')
73 73
74 74 c.users_list = User.query().order_by(User.username)\
75 75 .filter(User.username != User.DEFAULT_USER)\
76 76 .order_by(func.lower(User.username))\
77 77 .all()
78 78
79 79 users_data = []
80 80 total_records = len(c.users_list)
81 81 _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
82 82 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
83 83
84 84 grav_tmpl = '<div class="gravatar">%s</div>'
85 85
86 86 username = lambda user_id, username: (
87 87 template.get_def("user_name")
88 88 .render(user_id, username, _=_, h=h, c=c))
89 89
90 90 user_actions = lambda user_id, username: (
91 91 template.get_def("user_actions")
92 92 .render(user_id, username, _=_, h=h, c=c))
93 93
94 94 for user in c.users_list:
95 95 users_data.append({
96 96 "gravatar": grav_tmpl % h.gravatar(user.email, size=20),
97 97 "raw_name": user.username,
98 98 "username": username(user.user_id, user.username),
99 99 "firstname": h.escape(user.name),
100 100 "lastname": h.escape(user.lastname),
101 101 "last_login": h.fmt_date(user.last_login),
102 102 "last_login_raw": datetime_to_time(user.last_login),
103 103 "active": h.boolicon(user.active),
104 104 "admin": h.boolicon(user.admin),
105 105 "extern_type": user.extern_type,
106 106 "extern_name": user.extern_name,
107 107 "action": user_actions(user.user_id, user.username),
108 108 })
109 109
110 110 c.data = json.dumps({
111 111 "totalRecords": total_records,
112 112 "startIndex": 0,
113 113 "sort": None,
114 114 "dir": "asc",
115 115 "records": users_data
116 116 })
117 117
118 118 return render('admin/users/users.html')
119 119
120 120 def create(self):
121 121 """POST /users: Create a new item"""
122 122 # url('users')
123 123 c.default_extern_type = auth_modules.auth_internal.KallitheaAuthPlugin.name
124 124 user_model = UserModel()
125 125 user_form = UserForm()()
126 126 try:
127 127 form_result = user_form.to_python(dict(request.POST))
128 128 user = user_model.create(form_result)
129 129 usr = form_result['username']
130 130 action_logger(self.authuser, 'admin_created_user:%s' % usr,
131 131 None, self.ip_addr, self.sa)
132 132 h.flash(h.literal(_('Created user %s') % h.link_to(h.escape(usr), url('edit_user', id=user.user_id))),
133 133 category='success')
134 134 Session().commit()
135 135 except formencode.Invalid, errors:
136 136 return htmlfill.render(
137 137 render('admin/users/user_add.html'),
138 138 defaults=errors.value,
139 139 errors=errors.error_dict or {},
140 140 prefix_error=False,
141 141 encoding="UTF-8",
142 142 force_defaults=False)
143 143 except UserCreationError, e:
144 144 h.flash(e, 'error')
145 145 except Exception:
146 146 log.error(traceback.format_exc())
147 147 h.flash(_('Error occurred during creation of user %s') \
148 148 % request.POST.get('username'), category='error')
149 149 return redirect(url('users'))
150 150
151 151 def new(self, format='html'):
152 152 """GET /users/new: Form to create a new item"""
153 153 # url('new_user')
154 154 c.default_extern_type = auth_modules.auth_internal.KallitheaAuthPlugin.name
155 155 return render('admin/users/user_add.html')
156 156
157 157 def update(self, id):
158 158 """PUT /users/id: Update an existing item"""
159 159 # Forms posted to this method should contain a hidden field:
160 160 # <input type="hidden" name="_method" value="PUT" />
161 161 # Or using helpers:
162 162 # h.form(url('update_user', id=ID),
163 163 # method='put')
164 164 # url('user', id=ID)
165 165 c.active = 'profile'
166 166 user_model = UserModel()
167 167 c.user = user_model.get(id)
168 168 c.extern_type = c.user.extern_type
169 169 c.extern_name = c.user.extern_name
170 170 c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
171 171 _form = UserForm(edit=True, old_data={'user_id': id,
172 172 'email': c.user.email})()
173 173 form_result = {}
174 174 try:
175 175 form_result = _form.to_python(dict(request.POST))
176 176 skip_attrs = ['extern_type', 'extern_name']
177 177 #TODO: plugin should define if username can be updated
178 178 if c.extern_type != kallithea.EXTERN_TYPE_INTERNAL:
179 179 # forbid updating username for external accounts
180 180 skip_attrs.append('username')
181 181
182 182 user_model.update(id, form_result, skip_attrs=skip_attrs)
183 183 usr = form_result['username']
184 184 action_logger(self.authuser, 'admin_updated_user:%s' % usr,
185 185 None, self.ip_addr, self.sa)
186 186 h.flash(_('User updated successfully'), category='success')
187 187 Session().commit()
188 188 except formencode.Invalid, errors:
189 189 defaults = errors.value
190 190 e = errors.error_dict or {}
191 191 defaults.update({
192 192 'create_repo_perm': user_model.has_perm(id,
193 193 'hg.create.repository'),
194 194 'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'),
195 195 '_method': 'put'
196 196 })
197 197 return htmlfill.render(
198 198 render('admin/users/user_edit.html'),
199 199 defaults=defaults,
200 200 errors=e,
201 201 prefix_error=False,
202 202 encoding="UTF-8",
203 203 force_defaults=False)
204 204 except Exception:
205 205 log.error(traceback.format_exc())
206 206 h.flash(_('Error occurred during update of user %s') \
207 207 % form_result.get('username'), category='error')
208 208 return redirect(url('edit_user', id=id))
209 209
210 210 def delete(self, id):
211 211 """DELETE /users/id: Delete an existing item"""
212 212 # Forms posted to this method should contain a hidden field:
213 213 # <input type="hidden" name="_method" value="DELETE" />
214 214 # Or using helpers:
215 215 # h.form(url('delete_user', id=ID),
216 216 # method='delete')
217 217 # url('user', id=ID)
218 218 usr = User.get_or_404(id)
219 219 try:
220 220 UserModel().delete(usr)
221 221 Session().commit()
222 222 h.flash(_('Successfully deleted user'), category='success')
223 223 except (UserOwnsReposException, DefaultUserException), e:
224 224 h.flash(e, category='warning')
225 225 except Exception:
226 226 log.error(traceback.format_exc())
227 227 h.flash(_('An error occurred during deletion of user'),
228 228 category='error')
229 229 return redirect(url('users'))
230 230
231 231 def show(self, id, format='html'):
232 232 """GET /users/id: Show a specific item"""
233 233 # url('user', id=ID)
234 234 User.get_or_404(-1)
235 235
236 236 def edit(self, id, format='html'):
237 237 """GET /users/id/edit: Form to edit an existing item"""
238 238 # url('edit_user', id=ID)
239 239 c.user = User.get_or_404(id)
240 240 if c.user.username == User.DEFAULT_USER:
241 241 h.flash(_("You can't edit this user"), category='warning')
242 242 return redirect(url('users'))
243 243
244 244 c.active = 'profile'
245 245 c.extern_type = c.user.extern_type
246 246 c.extern_name = c.user.extern_name
247 247 c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
248 248
249 249 defaults = c.user.get_dict()
250 250 return htmlfill.render(
251 251 render('admin/users/user_edit.html'),
252 252 defaults=defaults,
253 253 encoding="UTF-8",
254 254 force_defaults=False)
255 255
256 256 def edit_advanced(self, id):
257 257 c.user = User.get_or_404(id)
258 258 if c.user.username == User.DEFAULT_USER:
259 259 h.flash(_("You can't edit this user"), category='warning')
260 260 return redirect(url('users'))
261 261
262 262 c.active = 'advanced'
263 263 c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
264 264
265 265 umodel = UserModel()
266 266 defaults = c.user.get_dict()
267 267 defaults.update({
268 268 'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
269 269 'create_user_group_perm': umodel.has_perm(c.user,
270 270 'hg.usergroup.create.true'),
271 271 'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
272 272 })
273 273 return htmlfill.render(
274 274 render('admin/users/user_edit.html'),
275 275 defaults=defaults,
276 276 encoding="UTF-8",
277 277 force_defaults=False)
278 278
279 279 def edit_api_keys(self, id):
280 280 c.user = User.get_or_404(id)
281 281 if c.user.username == User.DEFAULT_USER:
282 282 h.flash(_("You can't edit this user"), category='warning')
283 283 return redirect(url('users'))
284 284
285 285 c.active = 'api_keys'
286 286 show_expired = True
287 287 c.lifetime_values = [
288 288 (str(-1), _('forever')),
289 289 (str(5), _('5 minutes')),
290 290 (str(60), _('1 hour')),
291 291 (str(60 * 24), _('1 day')),
292 292 (str(60 * 24 * 30), _('1 month')),
293 293 ]
294 294 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
295 295 c.user_api_keys = ApiKeyModel().get_api_keys(c.user.user_id,
296 296 show_expired=show_expired)
297 297 defaults = c.user.get_dict()
298 298 return htmlfill.render(
299 299 render('admin/users/user_edit.html'),
300 300 defaults=defaults,
301 301 encoding="UTF-8",
302 302 force_defaults=False)
303 303
304 304 def add_api_key(self, id):
305 305 c.user = User.get_or_404(id)
306 306 if c.user.username == User.DEFAULT_USER:
307 307 h.flash(_("You can't edit this user"), category='warning')
308 308 return redirect(url('users'))
309 309
310 310 lifetime = safe_int(request.POST.get('lifetime'), -1)
311 311 description = request.POST.get('description')
312 312 ApiKeyModel().create(c.user.user_id, description, lifetime)
313 313 Session().commit()
314 314 h.flash(_("API key successfully created"), category='success')
315 315 return redirect(url('edit_user_api_keys', id=c.user.user_id))
316 316
317 317 def delete_api_key(self, id):
318 318 c.user = User.get_or_404(id)
319 319 if c.user.username == User.DEFAULT_USER:
320 320 h.flash(_("You can't edit this user"), category='warning')
321 321 return redirect(url('users'))
322 322
323 323 api_key = request.POST.get('del_api_key')
324 324 if request.POST.get('del_api_key_builtin'):
325 325 user = User.get(c.user.user_id)
326 326 if user:
327 327 user.api_key = generate_api_key(user.username)
328 328 Session().add(user)
329 329 Session().commit()
330 330 h.flash(_("API key successfully reset"), category='success')
331 331 elif api_key:
332 332 ApiKeyModel().delete(api_key, c.user.user_id)
333 333 Session().commit()
334 334 h.flash(_("API key successfully deleted"), category='success')
335 335
336 336 return redirect(url('edit_user_api_keys', id=c.user.user_id))
337 337
338 338 def update_account(self, id):
339 339 pass
340 340
341 341 def edit_perms(self, id):
342 342 c.user = User.get_or_404(id)
343 343 if c.user.username == User.DEFAULT_USER:
344 344 h.flash(_("You can't edit this user"), category='warning')
345 345 return redirect(url('users'))
346 346
347 347 c.active = 'perms'
348 348 c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
349 349
350 350 umodel = UserModel()
351 351 defaults = c.user.get_dict()
352 352 defaults.update({
353 353 'create_repo_perm': umodel.has_perm(c.user, 'hg.create.repository'),
354 354 'create_user_group_perm': umodel.has_perm(c.user,
355 355 'hg.usergroup.create.true'),
356 356 'fork_repo_perm': umodel.has_perm(c.user, 'hg.fork.repository'),
357 357 })
358 358 return htmlfill.render(
359 359 render('admin/users/user_edit.html'),
360 360 defaults=defaults,
361 361 encoding="UTF-8",
362 362 force_defaults=False)
363 363
364 364 def update_perms(self, id):
365 365 """PUT /users_perm/id: Update an existing item"""
366 366 # url('user_perm', id=ID, method='put')
367 367 user = User.get_or_404(id)
368 368
369 369 try:
370 370 form = CustomDefaultPermissionsForm()()
371 371 form_result = form.to_python(request.POST)
372 372
373 373 inherit_perms = form_result['inherit_default_permissions']
374 374 user.inherit_default_permissions = inherit_perms
375 375 Session().add(user)
376 376 user_model = UserModel()
377 377
378 378 defs = UserToPerm.query()\
379 379 .filter(UserToPerm.user == user)\
380 380 .all()
381 381 for ug in defs:
382 382 Session().delete(ug)
383 383
384 384 if form_result['create_repo_perm']:
385 385 user_model.grant_perm(id, 'hg.create.repository')
386 386 else:
387 387 user_model.grant_perm(id, 'hg.create.none')
388 388 if form_result['create_user_group_perm']:
389 389 user_model.grant_perm(id, 'hg.usergroup.create.true')
390 390 else:
391 391 user_model.grant_perm(id, 'hg.usergroup.create.false')
392 392 if form_result['fork_repo_perm']:
393 393 user_model.grant_perm(id, 'hg.fork.repository')
394 394 else:
395 395 user_model.grant_perm(id, 'hg.fork.none')
396 396 h.flash(_("Updated permissions"), category='success')
397 397 Session().commit()
398 398 except Exception:
399 399 log.error(traceback.format_exc())
400 400 h.flash(_('An error occurred during permissions saving'),
401 401 category='error')
402 402 return redirect(url('edit_user_perms', id=id))
403 403
404 404 def edit_emails(self, id):
405 405 c.user = User.get_or_404(id)
406 406 if c.user.username == User.DEFAULT_USER:
407 407 h.flash(_("You can't edit this user"), category='warning')
408 408 return redirect(url('users'))
409 409
410 410 c.active = 'emails'
411 411 c.user_email_map = UserEmailMap.query()\
412 412 .filter(UserEmailMap.user == c.user).all()
413 413
414 414 defaults = c.user.get_dict()
415 415 return htmlfill.render(
416 416 render('admin/users/user_edit.html'),
417 417 defaults=defaults,
418 418 encoding="UTF-8",
419 419 force_defaults=False)
420 420
421 421 def add_email(self, id):
422 422 """POST /user_emails:Add an existing item"""
423 423 # url('user_emails', id=ID, method='put')
424 424
425 425 email = request.POST.get('new_email')
426 426 user_model = UserModel()
427 427
428 428 try:
429 429 user_model.add_extra_email(id, email)
430 430 Session().commit()
431 431 h.flash(_("Added email %s to user") % email, category='success')
432 432 except formencode.Invalid, error:
433 433 msg = error.error_dict['email']
434 434 h.flash(msg, category='error')
435 435 except Exception:
436 436 log.error(traceback.format_exc())
437 437 h.flash(_('An error occurred during email saving'),
438 438 category='error')
439 439 return redirect(url('edit_user_emails', id=id))
440 440
441 441 def delete_email(self, id):
442 442 """DELETE /user_emails_delete/id: Delete an existing item"""
443 443 # url('user_emails_delete', id=ID, method='delete')
444 444 email_id = request.POST.get('del_email_id')
445 445 user_model = UserModel()
446 446 user_model.delete_extra_email(id, email_id)
447 447 Session().commit()
448 448 h.flash(_("Removed email from user"), category='success')
449 449 return redirect(url('edit_user_emails', id=id))
450 450
451 451 def edit_ips(self, id):
452 452 c.user = User.get_or_404(id)
453 453 if c.user.username == User.DEFAULT_USER:
454 454 h.flash(_("You can't edit this user"), category='warning')
455 455 return redirect(url('users'))
456 456
457 457 c.active = 'ips'
458 458 c.user_ip_map = UserIpMap.query()\
459 459 .filter(UserIpMap.user == c.user).all()
460 460
461 461 c.inherit_default_ips = c.user.inherit_default_permissions
462 462 c.default_user_ip_map = UserIpMap.query()\
463 463 .filter(UserIpMap.user == User.get_default_user()).all()
464 464
465 465 defaults = c.user.get_dict()
466 466 return htmlfill.render(
467 467 render('admin/users/user_edit.html'),
468 468 defaults=defaults,
469 469 encoding="UTF-8",
470 470 force_defaults=False)
471 471
472 472 def add_ip(self, id):
473 473 """POST /user_ips:Add an existing item"""
474 474 # url('user_ips', id=ID, method='put')
475 475
476 476 ip = request.POST.get('new_ip')
477 477 user_model = UserModel()
478 478
479 479 try:
480 480 user_model.add_extra_ip(id, ip)
481 481 Session().commit()
482 482 h.flash(_("Added ip %s to user whitelist") % ip, category='success')
483 483 except formencode.Invalid, error:
484 484 msg = error.error_dict['ip']
485 485 h.flash(msg, category='error')
486 486 except Exception:
487 487 log.error(traceback.format_exc())
488 488 h.flash(_('An error occurred during ip saving'),
489 489 category='error')
490 490
491 491 if 'default_user' in request.POST:
492 492 return redirect(url('admin_permissions_ips'))
493 493 return redirect(url('edit_user_ips', id=id))
494 494
495 495 def delete_ip(self, id):
496 496 """DELETE /user_ips_delete/id: Delete an existing item"""
497 497 # url('user_ips_delete', id=ID, method='delete')
498 498 ip_id = request.POST.get('del_ip_id')
499 499 user_model = UserModel()
500 500 user_model.delete_extra_ip(id, ip_id)
501 501 Session().commit()
502 h.flash(_("Removed ip address from user whitelist"), category='success')
502 h.flash(_("Removed IP address from user whitelist"), category='success')
503 503
504 504 if 'default_user' in request.POST:
505 505 return redirect(url('admin_permissions_ips'))
506 506 return redirect(url('edit_user_ips', id=id))
@@ -1,98 +1,98 b''
1 1 # -*- coding: utf-8 -*-
2 2 import urllib
3 3 import urllib2
4 4
5 5 API_SSL_SERVER = "https://www.google.com/recaptcha/api"
6 6 API_SERVER = "http://www.google.com/recaptcha/api"
7 7 VERIFY_SERVER = "www.google.com"
8 8
9 9
10 10 class RecaptchaResponse(object):
11 11 def __init__(self, is_valid, error_code=None):
12 12 self.is_valid = is_valid
13 13 self.error_code = error_code
14 14
15 15 def __repr__(self):
16 16 return '<RecaptchaResponse:%s>' % (self.is_valid)
17 17
18 18
19 19 def displayhtml(public_key, use_ssl=False, error=None):
20 20 """Gets the HTML to display for reCAPTCHA
21 21
22 22 public_key -- The public api key
23 23 use_ssl -- Should the request be sent over ssl?
24 24 error -- An error message to display (from RecaptchaResponse.error_code)"""
25 25
26 26 error_param = ''
27 27 if error:
28 28 error_param = '&error=%s' % error
29 29
30 30 if use_ssl:
31 31 server = API_SSL_SERVER
32 32 else:
33 33 server = API_SERVER
34 34
35 35 return """<script type="text/javascript" src="%(ApiServer)s/challenge?k=%(PublicKey)s%(ErrorParam)s"></script>
36 36
37 37 <noscript>
38 38 <iframe src="%(ApiServer)s/noscript?k=%(PublicKey)s%(ErrorParam)s" height="300" width="500" frameborder="0"></iframe><br />
39 39 <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
40 40 <input type='hidden' name='recaptcha_response_field' value='manual_challenge' />
41 41 </noscript>
42 42 """ % {
43 43 'ApiServer': server,
44 44 'PublicKey': public_key,
45 45 'ErrorParam': error_param,
46 46 }
47 47
48 48
49 49 def submit(recaptcha_challenge_field, recaptcha_response_field, private_key,
50 50 remoteip):
51 51 """
52 52 Submits a reCAPTCHA request for verification. Returns RecaptchaResponse
53 53 for the request
54 54
55 55 recaptcha_challenge_field -- The value of recaptcha_challenge_field from the form
56 56 recaptcha_response_field -- The value of recaptcha_response_field from the form
57 57 private_key -- your reCAPTCHA private key
58 remoteip -- the user's ip address
58 remoteip -- the user's IP address
59 59 """
60 60
61 61 if not (recaptcha_response_field and recaptcha_challenge_field and
62 62 len(recaptcha_response_field) and len(
63 63 recaptcha_challenge_field)):
64 64 return RecaptchaResponse(is_valid=False,
65 65 error_code='incorrect-captcha-sol')
66 66
67 67 def encode_if_necessary(s):
68 68 if isinstance(s, unicode):
69 69 return s.encode('utf-8')
70 70 return s
71 71
72 72 params = urllib.urlencode({
73 73 'privatekey': encode_if_necessary(private_key),
74 74 'remoteip': encode_if_necessary(remoteip),
75 75 'challenge': encode_if_necessary(recaptcha_challenge_field),
76 76 'response': encode_if_necessary(recaptcha_response_field),
77 77 })
78 78
79 79 request = urllib2.Request(
80 80 url="http://%s/recaptcha/api/verify" % VERIFY_SERVER,
81 81 data=params,
82 82 headers={
83 83 "Content-type": "application/x-www-form-urlencoded",
84 84 "User-agent": "reCAPTCHA Python"
85 85 }
86 86 )
87 87
88 88 httpresp = urllib2.urlopen(request)
89 89
90 90 return_values = httpresp.read().splitlines()
91 91 httpresp.close()
92 92
93 93 return_code = return_values[0]
94 94
95 95 if return_code == "true":
96 96 return RecaptchaResponse(is_valid=True)
97 97 else:
98 98 return RecaptchaResponse(is_valid=False, error_code=return_values[1])
@@ -1,872 +1,872 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.lib.utils
16 16 ~~~~~~~~~~~~~~~~~~~
17 17
18 18 Utilities library for Kallithea
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: Apr 18, 2010
23 23 :author: marcink
24 24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 25 :license: GPLv3, see LICENSE.md for more details.
26 26 """
27 27
28 28 import os
29 29 import re
30 30 import logging
31 31 import datetime
32 32 import traceback
33 33 import paste
34 34 import beaker
35 35 import tarfile
36 36 import shutil
37 37 import decorator
38 38 import warnings
39 39 from os.path import abspath
40 40 from os.path import dirname as dn, join as jn
41 41
42 42 from paste.script.command import Command, BadCommand
43 43
44 44 from webhelpers.text import collapse, remove_formatting, strip_tags
45 45 from beaker.cache import _cache_decorate
46 46
47 47 from kallithea import BRAND
48 48
49 49 from kallithea.lib.vcs.utils.hgcompat import ui, config
50 50 from kallithea.lib.vcs.utils.helpers import get_scm
51 51 from kallithea.lib.vcs.exceptions import VCSError
52 52
53 53 from kallithea.lib.caching_query import FromCache
54 54
55 55 from kallithea.model import meta
56 56 from kallithea.model.db import Repository, User, Ui, \
57 57 UserLog, RepoGroup, Setting, CacheInvalidation, UserGroup
58 58 from kallithea.model.meta import Session
59 59 from kallithea.model.repo_group import RepoGroupModel
60 60 from kallithea.lib.utils2 import safe_str, safe_unicode, get_current_authuser
61 61 from kallithea.lib.vcs.utils.fakemod import create_module
62 62
63 63 log = logging.getLogger(__name__)
64 64
65 65 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}_.*')
66 66
67 67
68 68 def recursive_replace(str_, replace=' '):
69 69 """
70 70 Recursive replace of given sign to just one instance
71 71
72 72 :param str_: given string
73 73 :param replace: char to find and replace multiple instances
74 74
75 75 Examples::
76 76 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
77 77 'Mighty-Mighty-Bo-sstones'
78 78 """
79 79
80 80 if str_.find(replace * 2) == -1:
81 81 return str_
82 82 else:
83 83 str_ = str_.replace(replace * 2, replace)
84 84 return recursive_replace(str_, replace)
85 85
86 86
87 87 def repo_name_slug(value):
88 88 """
89 89 Return slug of name of repository
90 90 This function is called on each creation/modification
91 91 of repository to prevent bad names in repo
92 92 """
93 93
94 94 slug = remove_formatting(value)
95 95 slug = strip_tags(slug)
96 96
97 97 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
98 98 slug = slug.replace(c, '-')
99 99 slug = recursive_replace(slug, '-')
100 100 slug = collapse(slug, '-')
101 101 return slug
102 102
103 103
104 104 #==============================================================================
105 105 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
106 106 #==============================================================================
107 107 def get_repo_slug(request):
108 108 _repo = request.environ['pylons.routes_dict'].get('repo_name')
109 109 if _repo:
110 110 _repo = _repo.rstrip('/')
111 111 return _repo
112 112
113 113
114 114 def get_repo_group_slug(request):
115 115 _group = request.environ['pylons.routes_dict'].get('group_name')
116 116 if _group:
117 117 _group = _group.rstrip('/')
118 118 return _group
119 119
120 120
121 121 def get_user_group_slug(request):
122 122 _group = request.environ['pylons.routes_dict'].get('id')
123 123 _group = UserGroup.get(_group)
124 124 if _group:
125 125 return _group.users_group_name
126 126 return None
127 127
128 128
129 129 def _extract_id_from_repo_name(repo_name):
130 130 if repo_name.startswith('/'):
131 131 repo_name = repo_name.lstrip('/')
132 132 by_id_match = re.match(r'^_(\d{1,})', repo_name)
133 133 if by_id_match:
134 134 return by_id_match.groups()[0]
135 135
136 136
137 137 def get_repo_by_id(repo_name):
138 138 """
139 139 Extracts repo_name by id from special urls. Example url is _11/repo_name
140 140
141 141 :param repo_name:
142 142 :return: repo_name if matched else None
143 143 """
144 144 _repo_id = _extract_id_from_repo_name(repo_name)
145 145 if _repo_id:
146 146 from kallithea.model.db import Repository
147 147 repo = Repository.get(_repo_id)
148 148 if repo:
149 149 # TODO: return repo instead of reponame? or would that be a layering violation?
150 150 return repo.repo_name
151 151 return None
152 152
153 153
154 154 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
155 155 """
156 156 Action logger for various actions made by users
157 157
158 158 :param user: user that made this action, can be a unique username string or
159 159 object containing user_id attribute
160 160 :param action: action to log, should be on of predefined unique actions for
161 161 easy translations
162 162 :param repo: string name of repository or object containing repo_id,
163 163 that action was made on
164 :param ipaddr: optional ip address from what the action was made
164 :param ipaddr: optional IP address from what the action was made
165 165 :param sa: optional sqlalchemy session
166 166
167 167 """
168 168
169 169 if not sa:
170 170 sa = meta.Session()
171 171 # if we don't get explicit IP address try to get one from registered user
172 172 # in tmpl context var
173 173 if not ipaddr:
174 174 ipaddr = getattr(get_current_authuser(), 'ip_addr', '')
175 175
176 176 if getattr(user, 'user_id', None):
177 177 user_obj = User.get(user.user_id)
178 178 elif isinstance(user, basestring):
179 179 user_obj = User.get_by_username(user)
180 180 else:
181 181 raise Exception('You have to provide a user object or a username')
182 182
183 183 if getattr(repo, 'repo_id', None):
184 184 repo_obj = Repository.get(repo.repo_id)
185 185 repo_name = repo_obj.repo_name
186 186 elif isinstance(repo, basestring):
187 187 repo_name = repo.lstrip('/')
188 188 repo_obj = Repository.get_by_repo_name(repo_name)
189 189 else:
190 190 repo_obj = None
191 191 repo_name = ''
192 192
193 193 user_log = UserLog()
194 194 user_log.user_id = user_obj.user_id
195 195 user_log.username = user_obj.username
196 196 user_log.action = safe_unicode(action)
197 197
198 198 user_log.repository = repo_obj
199 199 user_log.repository_name = repo_name
200 200
201 201 user_log.action_date = datetime.datetime.now()
202 202 user_log.user_ip = ipaddr
203 203 sa.add(user_log)
204 204
205 205 log.info('Logging action:%s on %s by user:%s ip:%s' %
206 206 (action, safe_unicode(repo), user_obj, ipaddr))
207 207 if commit:
208 208 sa.commit()
209 209
210 210
211 211 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
212 212 """
213 213 Scans given path for repos and return (name,(type,path)) tuple
214 214
215 215 :param path: path to scan for repositories
216 216 :param recursive: recursive search and return names with subdirs in front
217 217 """
218 218
219 219 # remove ending slash for better results
220 220 path = path.rstrip(os.sep)
221 221 log.debug('now scanning in %s location recursive:%s...' % (path, recursive))
222 222
223 223 def _get_repos(p):
224 224 if not os.access(p, os.R_OK) or not os.access(p, os.X_OK):
225 225 log.warning('ignoring repo path without access: %s' % (p,))
226 226 return
227 227 if not os.access(p, os.W_OK):
228 228 log.warning('repo path without write access: %s' % (p,))
229 229 for dirpath in os.listdir(p):
230 230 if os.path.isfile(os.path.join(p, dirpath)):
231 231 continue
232 232 cur_path = os.path.join(p, dirpath)
233 233
234 234 # skip removed repos
235 235 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
236 236 continue
237 237
238 238 #skip .<somethin> dirs
239 239 if dirpath.startswith('.'):
240 240 continue
241 241
242 242 try:
243 243 scm_info = get_scm(cur_path)
244 244 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
245 245 except VCSError:
246 246 if not recursive:
247 247 continue
248 248 #check if this dir containts other repos for recursive scan
249 249 rec_path = os.path.join(p, dirpath)
250 250 if not os.path.islink(rec_path) and os.path.isdir(rec_path):
251 251 for inner_scm in _get_repos(rec_path):
252 252 yield inner_scm
253 253
254 254 return _get_repos(path)
255 255
256 256
257 257 def is_valid_repo(repo_name, base_path, scm=None):
258 258 """
259 259 Returns True if given path is a valid repository False otherwise.
260 260 If scm param is given also compare if given scm is the same as expected
261 261 from scm parameter
262 262
263 263 :param repo_name:
264 264 :param base_path:
265 265 :param scm:
266 266
267 267 :return True: if given path is a valid repository
268 268 """
269 269 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
270 270
271 271 try:
272 272 scm_ = get_scm(full_path)
273 273 if scm:
274 274 return scm_[0] == scm
275 275 return True
276 276 except VCSError:
277 277 return False
278 278
279 279
280 280 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
281 281 """
282 282 Returns True if given path is a repository group False otherwise
283 283
284 284 :param repo_name:
285 285 :param base_path:
286 286 """
287 287 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
288 288
289 289 # check if it's not a repo
290 290 if is_valid_repo(repo_group_name, base_path):
291 291 return False
292 292
293 293 try:
294 294 # we need to check bare git repos at higher level
295 295 # since we might match branches/hooks/info/objects or possible
296 296 # other things inside bare git repo
297 297 get_scm(os.path.dirname(full_path))
298 298 return False
299 299 except VCSError:
300 300 pass
301 301
302 302 # check if it's a valid path
303 303 if skip_path_check or os.path.isdir(full_path):
304 304 return True
305 305
306 306 return False
307 307
308 308
309 309 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
310 310 while True:
311 311 ok = raw_input(prompt)
312 312 if ok in ('y', 'ye', 'yes'):
313 313 return True
314 314 if ok in ('n', 'no', 'nop', 'nope'):
315 315 return False
316 316 retries = retries - 1
317 317 if retries < 0:
318 318 raise IOError
319 319 print complaint
320 320
321 321 #propagated from mercurial documentation
322 322 ui_sections = ['alias', 'auth',
323 323 'decode/encode', 'defaults',
324 324 'diff', 'email',
325 325 'extensions', 'format',
326 326 'merge-patterns', 'merge-tools',
327 327 'hooks', 'http_proxy',
328 328 'smtp', 'patch',
329 329 'paths', 'profiling',
330 330 'server', 'trusted',
331 331 'ui', 'web', ]
332 332
333 333
334 334 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
335 335 """
336 336 A function that will read python rc files or database
337 337 and make an mercurial ui object from read options
338 338
339 339 :param path: path to mercurial config file
340 340 :param checkpaths: check the path
341 341 :param read_from: read from 'file' or 'db'
342 342 """
343 343
344 344 baseui = ui.ui()
345 345
346 346 # clean the baseui object
347 347 baseui._ocfg = config.config()
348 348 baseui._ucfg = config.config()
349 349 baseui._tcfg = config.config()
350 350
351 351 if read_from == 'file':
352 352 if not os.path.isfile(path):
353 353 log.debug('hgrc file is not present at %s, skipping...' % path)
354 354 return False
355 355 log.debug('reading hgrc from %s' % path)
356 356 cfg = config.config()
357 357 cfg.read(path)
358 358 for section in ui_sections:
359 359 for k, v in cfg.items(section):
360 360 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
361 361 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
362 362
363 363 elif read_from == 'db':
364 364 sa = meta.Session()
365 365 ret = sa.query(Ui).all()
366 366
367 367 hg_ui = ret
368 368 for ui_ in hg_ui:
369 369 if ui_.ui_active:
370 370 ui_val = safe_str(ui_.ui_value)
371 371 if ui_.ui_section == 'hooks' and BRAND != 'kallithea' and ui_val.startswith('python:' + BRAND + '.lib.hooks.'):
372 372 ui_val = ui_val.replace('python:' + BRAND + '.lib.hooks.', 'python:kallithea.lib.hooks.')
373 373 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
374 374 ui_.ui_key, ui_val)
375 375 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
376 376 ui_val)
377 377 if ui_.ui_key == 'push_ssl':
378 378 # force set push_ssl requirement to False, kallithea
379 379 # handles that
380 380 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
381 381 False)
382 382 if clear_session:
383 383 meta.Session.remove()
384 384
385 385 # prevent interactive questions for ssh password / passphrase
386 386 ssh = baseui.config('ui', 'ssh', default='ssh')
387 387 baseui.setconfig('ui', 'ssh', '%s -oBatchMode=yes -oIdentitiesOnly=yes' % ssh)
388 388
389 389 return baseui
390 390
391 391
392 392 def set_app_settings(config):
393 393 """
394 394 Updates pylons config with new settings from database
395 395
396 396 :param config:
397 397 """
398 398 hgsettings = Setting.get_app_settings()
399 399
400 400 for k, v in hgsettings.items():
401 401 config[k] = v
402 402
403 403
404 404 def set_vcs_config(config):
405 405 """
406 406 Patch VCS config with some Kallithea specific stuff
407 407
408 408 :param config: kallithea.CONFIG
409 409 """
410 410 import kallithea
411 411 from kallithea.lib.vcs import conf
412 412 from kallithea.lib.utils2 import aslist
413 413 conf.settings.BACKENDS = {
414 414 'hg': 'kallithea.lib.vcs.backends.hg.MercurialRepository',
415 415 'git': 'kallithea.lib.vcs.backends.git.GitRepository',
416 416 }
417 417
418 418 conf.settings.GIT_EXECUTABLE_PATH = config.get('git_path', 'git')
419 419 conf.settings.GIT_REV_FILTER = config.get('git_rev_filter', '--all').strip()
420 420 conf.settings.DEFAULT_ENCODINGS = aslist(config.get('default_encoding',
421 421 'utf8'), sep=',')
422 422
423 423
424 424 def map_groups(path):
425 425 """
426 426 Given a full path to a repository, create all nested groups that this
427 427 repo is inside. This function creates parent-child relationships between
428 428 groups and creates default perms for all new groups.
429 429
430 430 :param paths: full path to repository
431 431 """
432 432 sa = meta.Session()
433 433 groups = path.split(Repository.url_sep())
434 434 parent = None
435 435 group = None
436 436
437 437 # last element is repo in nested groups structure
438 438 groups = groups[:-1]
439 439 rgm = RepoGroupModel(sa)
440 440 owner = User.get_first_admin()
441 441 for lvl, group_name in enumerate(groups):
442 442 group_name = '/'.join(groups[:lvl] + [group_name])
443 443 group = RepoGroup.get_by_group_name(group_name)
444 444 desc = '%s group' % group_name
445 445
446 446 # skip folders that are now removed repos
447 447 if REMOVED_REPO_PAT.match(group_name):
448 448 break
449 449
450 450 if group is None:
451 451 log.debug('creating group level: %s group_name: %s'
452 452 % (lvl, group_name))
453 453 group = RepoGroup(group_name, parent)
454 454 group.group_description = desc
455 455 group.user = owner
456 456 sa.add(group)
457 457 perm_obj = rgm._create_default_perms(group)
458 458 sa.add(perm_obj)
459 459 sa.flush()
460 460
461 461 parent = group
462 462 return group
463 463
464 464
465 465 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
466 466 install_git_hook=False, user=None):
467 467 """
468 468 maps all repos given in initial_repo_list, non existing repositories
469 469 are created, if remove_obsolete is True it also check for db entries
470 470 that are not in initial_repo_list and removes them.
471 471
472 472 :param initial_repo_list: list of repositories found by scanning methods
473 473 :param remove_obsolete: check for obsolete entries in database
474 474 :param install_git_hook: if this is True, also check and install githook
475 475 for a repo if missing
476 476 """
477 477 from kallithea.model.repo import RepoModel
478 478 from kallithea.model.scm import ScmModel
479 479 sa = meta.Session()
480 480 repo_model = RepoModel()
481 481 if user is None:
482 482 user = User.get_first_admin()
483 483 added = []
484 484
485 485 ##creation defaults
486 486 defs = Setting.get_default_repo_settings(strip_prefix=True)
487 487 enable_statistics = defs.get('repo_enable_statistics')
488 488 enable_locking = defs.get('repo_enable_locking')
489 489 enable_downloads = defs.get('repo_enable_downloads')
490 490 private = defs.get('repo_private')
491 491
492 492 for name, repo in initial_repo_list.items():
493 493 group = map_groups(name)
494 494 unicode_name = safe_unicode(name)
495 495 db_repo = repo_model.get_by_repo_name(unicode_name)
496 496 # found repo that is on filesystem not in Kallithea database
497 497 if not db_repo:
498 498 log.info('repository %s not found, creating now' % name)
499 499 added.append(name)
500 500 desc = (repo.description
501 501 if repo.description != 'unknown'
502 502 else '%s repository' % name)
503 503
504 504 new_repo = repo_model._create_repo(
505 505 repo_name=name,
506 506 repo_type=repo.alias,
507 507 description=desc,
508 508 repo_group=getattr(group, 'group_id', None),
509 509 owner=user,
510 510 enable_locking=enable_locking,
511 511 enable_downloads=enable_downloads,
512 512 enable_statistics=enable_statistics,
513 513 private=private,
514 514 state=Repository.STATE_CREATED
515 515 )
516 516 sa.commit()
517 517 # we added that repo just now, and make sure it has githook
518 518 # installed, and updated server info
519 519 if new_repo.repo_type == 'git':
520 520 git_repo = new_repo.scm_instance
521 521 ScmModel().install_git_hook(git_repo)
522 522 # update repository server-info
523 523 log.debug('Running update server info')
524 524 git_repo._update_server_info()
525 525 new_repo.update_changeset_cache()
526 526 elif install_git_hook:
527 527 if db_repo.repo_type == 'git':
528 528 ScmModel().install_git_hook(db_repo.scm_instance)
529 529
530 530 removed = []
531 531 # remove from database those repositories that are not in the filesystem
532 532 for repo in sa.query(Repository).all():
533 533 if repo.repo_name not in initial_repo_list.keys():
534 534 if remove_obsolete:
535 535 log.debug("Removing non-existing repository found in db `%s`" %
536 536 repo.repo_name)
537 537 try:
538 538 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
539 539 sa.commit()
540 540 except Exception:
541 541 #don't hold further removals on error
542 542 log.error(traceback.format_exc())
543 543 sa.rollback()
544 544 removed.append(repo.repo_name)
545 545 return added, removed
546 546
547 547
548 548 # set cache regions for beaker so celery can utilise it
549 549 def add_cache(settings):
550 550 cache_settings = {'regions': None}
551 551 for key in settings.keys():
552 552 for prefix in ['beaker.cache.', 'cache.']:
553 553 if key.startswith(prefix):
554 554 name = key.split(prefix)[1].strip()
555 555 cache_settings[name] = settings[key].strip()
556 556 if cache_settings['regions']:
557 557 for region in cache_settings['regions'].split(','):
558 558 region = region.strip()
559 559 region_settings = {}
560 560 for key, value in cache_settings.items():
561 561 if key.startswith(region):
562 562 region_settings[key.split('.')[1]] = value
563 563 region_settings['expire'] = int(region_settings.get('expire',
564 564 60))
565 565 region_settings.setdefault('lock_dir',
566 566 cache_settings.get('lock_dir'))
567 567 region_settings.setdefault('data_dir',
568 568 cache_settings.get('data_dir'))
569 569
570 570 if 'type' not in region_settings:
571 571 region_settings['type'] = cache_settings.get('type',
572 572 'memory')
573 573 beaker.cache.cache_regions[region] = region_settings
574 574
575 575
576 576 def load_rcextensions(root_path):
577 577 import kallithea
578 578 from kallithea.config import conf
579 579
580 580 path = os.path.join(root_path, 'rcextensions', '__init__.py')
581 581 if os.path.isfile(path):
582 582 rcext = create_module('rc', path)
583 583 EXT = kallithea.EXTENSIONS = rcext
584 584 log.debug('Found rcextensions now loading %s...' % rcext)
585 585
586 586 # Additional mappings that are not present in the pygments lexers
587 587 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
588 588
589 589 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
590 590
591 591 if getattr(EXT, 'INDEX_EXTENSIONS', []):
592 592 log.debug('settings custom INDEX_EXTENSIONS')
593 593 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
594 594
595 595 #ADDITIONAL MAPPINGS
596 596 log.debug('adding extra into INDEX_EXTENSIONS')
597 597 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
598 598
599 599 # auto check if the module is not missing any data, set to default if is
600 600 # this will help autoupdate new feature of rcext module
601 601 #from kallithea.config import rcextensions
602 602 #for k in dir(rcextensions):
603 603 # if not k.startswith('_') and not hasattr(EXT, k):
604 604 # setattr(EXT, k, getattr(rcextensions, k))
605 605
606 606
607 607 def get_custom_lexer(extension):
608 608 """
609 609 returns a custom lexer if it's defined in rcextensions module, or None
610 610 if there's no custom lexer defined
611 611 """
612 612 import kallithea
613 613 from pygments import lexers
614 614 #check if we didn't define this extension as other lexer
615 615 if kallithea.EXTENSIONS and extension in kallithea.EXTENSIONS.EXTRA_LEXERS:
616 616 _lexer_name = kallithea.EXTENSIONS.EXTRA_LEXERS[extension]
617 617 return lexers.get_lexer_by_name(_lexer_name)
618 618
619 619
620 620 #==============================================================================
621 621 # TEST FUNCTIONS AND CREATORS
622 622 #==============================================================================
623 623 def create_test_index(repo_location, config, full_index):
624 624 """
625 625 Makes default test index
626 626
627 627 :param config: test config
628 628 :param full_index:
629 629 """
630 630
631 631 from kallithea.lib.indexers.daemon import WhooshIndexingDaemon
632 632 from kallithea.lib.pidlock import DaemonLock, LockHeld
633 633
634 634 repo_location = repo_location
635 635
636 636 index_location = os.path.join(config['app_conf']['index_dir'])
637 637 if not os.path.exists(index_location):
638 638 os.makedirs(index_location)
639 639
640 640 try:
641 641 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
642 642 WhooshIndexingDaemon(index_location=index_location,
643 643 repo_location=repo_location)\
644 644 .run(full_index=full_index)
645 645 l.release()
646 646 except LockHeld:
647 647 pass
648 648
649 649
650 650 def create_test_env(repos_test_path, config):
651 651 """
652 652 Makes a fresh database and
653 653 install test repository into tmp dir
654 654 """
655 655 from kallithea.lib.db_manage import DbManage
656 656 from kallithea.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
657 657
658 658 # PART ONE create db
659 659 dbconf = config['sqlalchemy.db1.url']
660 660 log.debug('making test db %s' % dbconf)
661 661
662 662 # create test dir if it doesn't exist
663 663 if not os.path.isdir(repos_test_path):
664 664 log.debug('Creating testdir %s' % repos_test_path)
665 665 os.makedirs(repos_test_path)
666 666
667 667 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
668 668 tests=True)
669 669 dbmanage.create_tables(override=True)
670 670 # for tests dynamically set new root paths based on generated content
671 671 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
672 672 dbmanage.create_default_user()
673 673 dbmanage.admin_prompt()
674 674 dbmanage.create_permissions()
675 675 dbmanage.populate_default_permissions()
676 676 Session().commit()
677 677 # PART TWO make test repo
678 678 log.debug('making test vcs repositories')
679 679
680 680 idx_path = config['app_conf']['index_dir']
681 681 data_path = config['app_conf']['cache_dir']
682 682
683 683 #clean index and data
684 684 if idx_path and os.path.exists(idx_path):
685 685 log.debug('remove %s' % idx_path)
686 686 shutil.rmtree(idx_path)
687 687
688 688 if data_path and os.path.exists(data_path):
689 689 log.debug('remove %s' % data_path)
690 690 shutil.rmtree(data_path)
691 691
692 692 #CREATE DEFAULT TEST REPOS
693 693 cur_dir = dn(dn(abspath(__file__)))
694 694 tar = tarfile.open(jn(cur_dir, 'tests', 'fixtures', "vcs_test_hg.tar.gz"))
695 695 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
696 696 tar.close()
697 697
698 698 cur_dir = dn(dn(abspath(__file__)))
699 699 tar = tarfile.open(jn(cur_dir, 'tests', 'fixtures', "vcs_test_git.tar.gz"))
700 700 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
701 701 tar.close()
702 702
703 703 #LOAD VCS test stuff
704 704 from kallithea.tests.vcs import setup_package
705 705 setup_package()
706 706
707 707
708 708 #==============================================================================
709 709 # PASTER COMMANDS
710 710 #==============================================================================
711 711 class BasePasterCommand(Command):
712 712 """
713 713 Abstract Base Class for paster commands.
714 714
715 715 The celery commands are somewhat aggressive about loading
716 716 celery.conf, and since our module sets the `CELERY_LOADER`
717 717 environment variable to our loader, we have to bootstrap a bit and
718 718 make sure we've had a chance to load the pylons config off of the
719 719 command line, otherwise everything fails.
720 720 """
721 721 min_args = 1
722 722 min_args_error = "Please provide a paster config file as an argument."
723 723 takes_config_file = 1
724 724 requires_config_file = True
725 725
726 726 def notify_msg(self, msg, log=False):
727 727 """Make a notification to user, additionally if logger is passed
728 728 it logs this action using given logger
729 729
730 730 :param msg: message that will be printed to user
731 731 :param log: logging instance, to use to additionally log this message
732 732
733 733 """
734 734 if log and isinstance(log, logging):
735 735 log(msg)
736 736
737 737 def run(self, args):
738 738 """
739 739 Overrides Command.run
740 740
741 741 Checks for a config file argument and loads it.
742 742 """
743 743 if len(args) < self.min_args:
744 744 raise BadCommand(
745 745 self.min_args_error % {'min_args': self.min_args,
746 746 'actual_args': len(args)})
747 747
748 748 # Decrement because we're going to lob off the first argument.
749 749 # @@ This is hacky
750 750 self.min_args -= 1
751 751 self.bootstrap_config(args[0])
752 752 self.update_parser()
753 753 return super(BasePasterCommand, self).run(args[1:])
754 754
755 755 def update_parser(self):
756 756 """
757 757 Abstract method. Allows for the class's parser to be updated
758 758 before the superclass's `run` method is called. Necessary to
759 759 allow options/arguments to be passed through to the underlying
760 760 celery command.
761 761 """
762 762 raise NotImplementedError("Abstract Method.")
763 763
764 764 def bootstrap_config(self, conf):
765 765 """
766 766 Loads the pylons configuration.
767 767 """
768 768 from pylons import config as pylonsconfig
769 769
770 770 self.path_to_ini_file = os.path.realpath(conf)
771 771 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
772 772 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
773 773
774 774 def _init_session(self):
775 775 """
776 776 Inits SqlAlchemy Session
777 777 """
778 778 logging.config.fileConfig(self.path_to_ini_file)
779 779 from pylons import config
780 780 from kallithea.model import init_model
781 781 from kallithea.lib.utils2 import engine_from_config
782 782
783 783 #get to remove repos !!
784 784 add_cache(config)
785 785 engine = engine_from_config(config, 'sqlalchemy.db1.')
786 786 init_model(engine)
787 787
788 788
789 789 def check_git_version():
790 790 """
791 791 Checks what version of git is installed in system, and issues a warning
792 792 if it's too old for Kallithea to work properly.
793 793 """
794 794 from kallithea import BACKENDS
795 795 from kallithea.lib.vcs.backends.git.repository import GitRepository
796 796 from kallithea.lib.vcs.conf import settings
797 797 from distutils.version import StrictVersion
798 798
799 799 if 'git' not in BACKENDS:
800 800 return None
801 801
802 802 stdout, stderr = GitRepository._run_git_command('--version', _bare=True,
803 803 _safe=True)
804 804
805 805 m = re.search("\d+.\d+.\d+", stdout)
806 806 if m:
807 807 ver = StrictVersion(m.group(0))
808 808 else:
809 809 ver = StrictVersion('0.0.0')
810 810
811 811 req_ver = StrictVersion('1.7.4')
812 812
813 813 log.debug('Git executable: "%s" version %s detected: %s'
814 814 % (settings.GIT_EXECUTABLE_PATH, ver, stdout))
815 815 if stderr:
816 816 log.warning('Error detecting git version: %r' % stderr)
817 817 elif ver < req_ver:
818 818 log.warning('Kallithea detected git version %s, which is too old '
819 819 'for the system to function properly. '
820 820 'Please upgrade to version %s or later.' % (ver, req_ver))
821 821 return ver
822 822
823 823
824 824 @decorator.decorator
825 825 def jsonify(func, *args, **kwargs):
826 826 """Action decorator that formats output for JSON
827 827
828 828 Given a function that will return content, this decorator will turn
829 829 the result into JSON, with a content-type of 'application/json' and
830 830 output it.
831 831
832 832 """
833 833 from pylons.decorators.util import get_pylons
834 834 from kallithea.lib.compat import json
835 835 pylons = get_pylons(args)
836 836 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
837 837 data = func(*args, **kwargs)
838 838 if isinstance(data, (list, tuple)):
839 839 msg = "JSON responses with Array envelopes are susceptible to " \
840 840 "cross-site data leak attacks, see " \
841 841 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
842 842 warnings.warn(msg, Warning, 2)
843 843 log.warning(msg)
844 844 log.debug("Returning JSON wrapped action output")
845 845 return json.dumps(data, encoding='utf-8')
846 846
847 847
848 848 def conditional_cache(region, prefix, condition, func):
849 849 """
850 850
851 851 Conditional caching function use like::
852 852 def _c(arg):
853 853 #heavy computation function
854 854 return data
855 855
856 856 # denpending from condition the compute is wrapped in cache or not
857 857 compute = conditional_cache('short_term', 'cache_desc', codnition=True, func=func)
858 858 return compute(arg)
859 859
860 860 :param region: name of cache region
861 861 :param prefix: cache region prefix
862 862 :param condition: condition for cache to be triggered, and return data cached
863 863 :param func: wrapped heavy function to compute
864 864
865 865 """
866 866 wrapped = func
867 867 if condition:
868 868 log.debug('conditional_cache: True, wrapping call of '
869 869 'func: %s into %s region cache' % (region, func))
870 870 wrapped = _cache_decorate((prefix,), None, None, region)(func)
871 871
872 872 return wrapped
@@ -1,476 +1,476 b''
1 1 # -*- coding: utf-8 -*-
2 2 # This program is free software: you can redistribute it and/or modify
3 3 # it under the terms of the GNU General Public License as published by
4 4 # the Free Software Foundation, either version 3 of the License, or
5 5 # (at your option) any later version.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 """
15 15 kallithea.model.user
16 16 ~~~~~~~~~~~~~~~~~~~~
17 17
18 18 users model for Kallithea
19 19
20 20 This file was forked by the Kallithea project in July 2014.
21 21 Original author and date, and relevant copyright and licensing information is below:
22 22 :created_on: Apr 9, 2010
23 23 :author: marcink
24 24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 25 :license: GPLv3, see LICENSE.md for more details.
26 26 """
27 27
28 28
29 29 import logging
30 30 import traceback
31 31 from pylons.i18n.translation import _
32 32
33 33 from sqlalchemy.exc import DatabaseError
34 34
35 35 from kallithea import EXTERN_TYPE_INTERNAL
36 36 from kallithea.lib.utils2 import safe_unicode, generate_api_key, get_current_authuser
37 37 from kallithea.lib.caching_query import FromCache
38 38 from kallithea.model import BaseModel
39 39 from kallithea.model.db import User, UserToPerm, Notification, \
40 40 UserEmailMap, UserIpMap
41 41 from kallithea.lib.exceptions import DefaultUserException, \
42 42 UserOwnsReposException
43 43 from kallithea.model.meta import Session
44 44
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class UserModel(BaseModel):
50 50 cls = User
51 51
52 52 def get(self, user_id, cache=False):
53 53 user = self.sa.query(User)
54 54 if cache:
55 55 user = user.options(FromCache("sql_cache_short",
56 56 "get_user_%s" % user_id))
57 57 return user.get(user_id)
58 58
59 59 def get_user(self, user):
60 60 return self._get_user(user)
61 61
62 62 def get_by_username(self, username, cache=False, case_insensitive=False):
63 63
64 64 if case_insensitive:
65 65 user = self.sa.query(User).filter(User.username.ilike(username))
66 66 else:
67 67 user = self.sa.query(User)\
68 68 .filter(User.username == username)
69 69 if cache:
70 70 user = user.options(FromCache("sql_cache_short",
71 71 "get_user_%s" % username))
72 72 return user.scalar()
73 73
74 74 def get_by_email(self, email, cache=False, case_insensitive=False):
75 75 return User.get_by_email(email, case_insensitive, cache)
76 76
77 77 def get_by_api_key(self, api_key, cache=False):
78 78 return User.get_by_api_key(api_key, cache)
79 79
80 80 def create(self, form_data, cur_user=None):
81 81 if not cur_user:
82 82 cur_user = getattr(get_current_authuser(), 'username', None)
83 83
84 84 from kallithea.lib.hooks import log_create_user, \
85 85 check_allowed_create_user
86 86 _fd = form_data
87 87 user_data = {
88 88 'username': _fd['username'],
89 89 'password': _fd['password'],
90 90 'email': _fd['email'],
91 91 'firstname': _fd['firstname'],
92 92 'lastname': _fd['lastname'],
93 93 'active': _fd['active'],
94 94 'admin': False
95 95 }
96 96 # raises UserCreationError if it's not allowed
97 97 check_allowed_create_user(user_data, cur_user)
98 98 from kallithea.lib.auth import get_crypt_password
99 99
100 100 new_user = User()
101 101 for k, v in form_data.items():
102 102 if k == 'password':
103 103 v = get_crypt_password(v)
104 104 if k == 'firstname':
105 105 k = 'name'
106 106 setattr(new_user, k, v)
107 107
108 108 new_user.api_key = generate_api_key(form_data['username'])
109 109 self.sa.add(new_user)
110 110
111 111 log_create_user(new_user.get_dict(), cur_user)
112 112 return new_user
113 113
114 114 def create_or_update(self, username, password, email, firstname='',
115 115 lastname='', active=True, admin=False,
116 116 extern_type=None, extern_name=None, cur_user=None):
117 117 """
118 118 Creates a new instance if not found, or updates current one
119 119
120 120 :param username:
121 121 :param password:
122 122 :param email:
123 123 :param active:
124 124 :param firstname:
125 125 :param lastname:
126 126 :param active:
127 127 :param admin:
128 128 :param extern_name:
129 129 :param extern_type:
130 130 :param cur_user:
131 131 """
132 132 if not cur_user:
133 133 cur_user = getattr(get_current_authuser(), 'username', None)
134 134
135 135 from kallithea.lib.auth import get_crypt_password, check_password
136 136 from kallithea.lib.hooks import log_create_user, \
137 137 check_allowed_create_user
138 138 user_data = {
139 139 'username': username, 'password': password,
140 140 'email': email, 'firstname': firstname, 'lastname': lastname,
141 141 'active': active, 'admin': admin
142 142 }
143 143 # raises UserCreationError if it's not allowed
144 144 check_allowed_create_user(user_data, cur_user)
145 145
146 146 log.debug('Checking for %s account in Kallithea database' % username)
147 147 user = User.get_by_username(username, case_insensitive=True)
148 148 if user is None:
149 149 log.debug('creating new user %s' % username)
150 150 new_user = User()
151 151 edit = False
152 152 else:
153 153 log.debug('updating user %s' % username)
154 154 new_user = user
155 155 edit = True
156 156
157 157 try:
158 158 new_user.username = username
159 159 new_user.admin = admin
160 160 new_user.email = email
161 161 new_user.active = active
162 162 new_user.extern_name = safe_unicode(extern_name) \
163 163 if extern_name else None
164 164 new_user.extern_type = safe_unicode(extern_type) \
165 165 if extern_type else None
166 166 new_user.name = firstname
167 167 new_user.lastname = lastname
168 168
169 169 if not edit:
170 170 new_user.api_key = generate_api_key(username)
171 171
172 172 # set password only if creating an user or password is changed
173 173 password_change = new_user.password and \
174 174 not check_password(password, new_user.password)
175 175 if not edit or password_change:
176 176 reason = 'new password' if edit else 'new user'
177 177 log.debug('Updating password reason=>%s' % (reason,))
178 178 new_user.password = get_crypt_password(password) \
179 179 if password else None
180 180
181 181 self.sa.add(new_user)
182 182
183 183 if not edit:
184 184 log_create_user(new_user.get_dict(), cur_user)
185 185 return new_user
186 186 except (DatabaseError,):
187 187 log.error(traceback.format_exc())
188 188 raise
189 189
190 190 def create_registration(self, form_data):
191 191 from kallithea.model.notification import NotificationModel
192 192 import kallithea.lib.helpers as h
193 193
194 194 form_data['admin'] = False
195 195 form_data['extern_name'] = EXTERN_TYPE_INTERNAL
196 196 form_data['extern_type'] = EXTERN_TYPE_INTERNAL
197 197 new_user = self.create(form_data)
198 198
199 199 self.sa.add(new_user)
200 200 self.sa.flush()
201 201
202 202 # notification to admins
203 203 subject = _('New user registration')
204 204 body = (
205 205 'New user registration\n'
206 206 '---------------------\n'
207 207 '- Username: {user.username}\n'
208 208 '- Full Name: {user.full_name}\n'
209 209 '- Email: {user.email}\n'
210 210 ).format(user=new_user)
211 211 edit_url = h.canonical_url('edit_user', id=new_user.user_id)
212 212 email_kwargs = {
213 213 'registered_user_url': edit_url,
214 214 'new_username': new_user.username}
215 215 NotificationModel().create(created_by=new_user, subject=subject,
216 216 body=body, recipients=None,
217 217 type_=Notification.TYPE_REGISTRATION,
218 218 email_kwargs=email_kwargs)
219 219
220 220 def update(self, user_id, form_data, skip_attrs=[]):
221 221 from kallithea.lib.auth import get_crypt_password
222 222
223 223 user = self.get(user_id, cache=False)
224 224 if user.username == User.DEFAULT_USER:
225 225 raise DefaultUserException(
226 226 _("You can't Edit this user since it's "
227 227 "crucial for entire application"))
228 228
229 229 for k, v in form_data.items():
230 230 if k in skip_attrs:
231 231 continue
232 232 if k == 'new_password' and v:
233 233 user.password = get_crypt_password(v)
234 234 else:
235 235 # old legacy thing orm models store firstname as name,
236 236 # need proper refactor to username
237 237 if k == 'firstname':
238 238 k = 'name'
239 239 setattr(user, k, v)
240 240 self.sa.add(user)
241 241
242 242 def update_user(self, user, **kwargs):
243 243 from kallithea.lib.auth import get_crypt_password
244 244
245 245 user = self._get_user(user)
246 246 if user.username == User.DEFAULT_USER:
247 247 raise DefaultUserException(
248 248 _("You can't Edit this user since it's"
249 249 " crucial for entire application")
250 250 )
251 251
252 252 for k, v in kwargs.items():
253 253 if k == 'password' and v:
254 254 v = get_crypt_password(v)
255 255
256 256 setattr(user, k, v)
257 257 self.sa.add(user)
258 258 return user
259 259
260 260 def delete(self, user, cur_user=None):
261 261 if not cur_user:
262 262 cur_user = getattr(get_current_authuser(), 'username', None)
263 263 user = self._get_user(user)
264 264
265 265 if user.username == User.DEFAULT_USER:
266 266 raise DefaultUserException(
267 267 _(u"You can't remove this user since it's"
268 268 " crucial for entire application"))
269 269 if user.repositories:
270 270 repos = [x.repo_name for x in user.repositories]
271 271 raise UserOwnsReposException(
272 272 _(u'User "%s" still owns %s repositories and cannot be '
273 273 'removed. Switch owners or remove those repositories: %s')
274 274 % (user.username, len(repos), ', '.join(repos)))
275 275 if user.repo_groups:
276 276 repogroups = [x.group_name for x in user.repo_groups]
277 277 raise UserOwnsReposException(_(
278 278 'User "%s" still owns %s repository groups and cannot be '
279 279 'removed. Switch owners or remove those repository groups: %s')
280 280 % (user.username, len(repogroups), ', '.join(repogroups)))
281 281 if user.user_groups:
282 282 usergroups = [x.users_group_name for x in user.user_groups]
283 283 raise UserOwnsReposException(
284 284 _('User "%s" still owns %s user groups and cannot be '
285 285 'removed. Switch owners or remove those user groups: %s')
286 286 % (user.username, len(usergroups), ', '.join(usergroups)))
287 287 self.sa.delete(user)
288 288
289 289 from kallithea.lib.hooks import log_delete_user
290 290 log_delete_user(user.get_dict(), cur_user)
291 291
292 292 def reset_password_link(self, data):
293 293 from kallithea.lib.celerylib import tasks, run_task
294 294 from kallithea.model.notification import EmailNotificationModel
295 295 import kallithea.lib.helpers as h
296 296
297 297 user_email = data['email']
298 298 user = User.get_by_email(user_email)
299 299 if user:
300 300 log.debug('password reset user found %s' % user)
301 301 link = h.canonical_url('reset_password_confirmation',
302 302 key=user.api_key)
303 303 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
304 304 body = EmailNotificationModel().get_email_tmpl(
305 305 reg_type, 'txt',
306 306 user=user.short_contact,
307 307 reset_url=link)
308 308 html_body = EmailNotificationModel().get_email_tmpl(
309 309 reg_type, 'html',
310 310 user=user.short_contact,
311 311 reset_url=link)
312 312 log.debug('sending email')
313 313 run_task(tasks.send_email, [user_email],
314 314 _("Password reset link"), body, html_body)
315 315 log.info('send new password mail to %s' % user_email)
316 316 else:
317 317 log.debug("password reset email %s not found" % user_email)
318 318
319 319 return True
320 320
321 321 def reset_password(self, data):
322 322 from kallithea.lib.celerylib import tasks, run_task
323 323 from kallithea.lib import auth
324 324 user_email = data['email']
325 325 user = User.get_by_email(user_email)
326 326 new_passwd = auth.PasswordGenerator().gen_password(
327 327 8, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
328 328 if user:
329 329 user.password = auth.get_crypt_password(new_passwd)
330 330 Session().add(user)
331 331 Session().commit()
332 332 log.info('change password for %s' % user_email)
333 333 if new_passwd is None:
334 334 raise Exception('unable to generate new password')
335 335
336 336 run_task(tasks.send_email, [user_email],
337 337 _('Your new password'),
338 338 _('Your new Kallithea password:%s') % (new_passwd,))
339 339 log.info('send new password mail to %s' % user_email)
340 340
341 341 return True
342 342
343 343 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
344 344 """
345 345 Fetches auth_user by user_id,or api_key if present.
346 346 Fills auth_user attributes with those taken from database.
347 347 Additionally sets is_authenticated if lookup fails
348 348 present in database
349 349
350 350 :param auth_user: instance of user to set attributes
351 351 :param user_id: user id to fetch by
352 352 :param api_key: API key to fetch by
353 353 :param username: username to fetch by
354 354 """
355 355 if user_id is None and api_key is None and username is None:
356 356 raise Exception('You need to pass user_id, api_key or username')
357 357
358 358 dbuser = None
359 359 if user_id is not None:
360 360 dbuser = self.get(user_id)
361 361 elif api_key is not None:
362 362 dbuser = self.get_by_api_key(api_key)
363 363 elif username is not None:
364 364 dbuser = self.get_by_username(username)
365 365
366 366 if dbuser is not None and dbuser.active:
367 367 log.debug('filling %s data' % dbuser)
368 368 for k, v in dbuser.get_dict().iteritems():
369 369 if k not in ['api_keys', 'permissions']:
370 370 setattr(auth_user, k, v)
371 371 return True
372 372 return False
373 373
374 374 def has_perm(self, user, perm):
375 375 perm = self._get_perm(perm)
376 376 user = self._get_user(user)
377 377
378 378 return UserToPerm.query().filter(UserToPerm.user == user)\
379 379 .filter(UserToPerm.permission == perm).scalar() is not None
380 380
381 381 def grant_perm(self, user, perm):
382 382 """
383 383 Grant user global permissions
384 384
385 385 :param user:
386 386 :param perm:
387 387 """
388 388 user = self._get_user(user)
389 389 perm = self._get_perm(perm)
390 390 # if this permission is already granted skip it
391 391 _perm = UserToPerm.query()\
392 392 .filter(UserToPerm.user == user)\
393 393 .filter(UserToPerm.permission == perm)\
394 394 .scalar()
395 395 if _perm:
396 396 return
397 397 new = UserToPerm()
398 398 new.user = user
399 399 new.permission = perm
400 400 self.sa.add(new)
401 401 return new
402 402
403 403 def revoke_perm(self, user, perm):
404 404 """
405 405 Revoke users global permissions
406 406
407 407 :param user:
408 408 :param perm:
409 409 """
410 410 user = self._get_user(user)
411 411 perm = self._get_perm(perm)
412 412
413 413 UserToPerm.query().filter(
414 414 UserToPerm.user == user,
415 415 UserToPerm.permission == perm,
416 416 ).delete()
417 417
418 418 def add_extra_email(self, user, email):
419 419 """
420 420 Adds email address to UserEmailMap
421 421
422 422 :param user:
423 423 :param email:
424 424 """
425 425 from kallithea.model import forms
426 426 form = forms.UserExtraEmailForm()()
427 427 data = form.to_python(dict(email=email))
428 428 user = self._get_user(user)
429 429
430 430 obj = UserEmailMap()
431 431 obj.user = user
432 432 obj.email = data['email']
433 433 self.sa.add(obj)
434 434 return obj
435 435
436 436 def delete_extra_email(self, user, email_id):
437 437 """
438 438 Removes email address from UserEmailMap
439 439
440 440 :param user:
441 441 :param email_id:
442 442 """
443 443 user = self._get_user(user)
444 444 obj = UserEmailMap.query().get(email_id)
445 445 if obj:
446 446 self.sa.delete(obj)
447 447
448 448 def add_extra_ip(self, user, ip):
449 449 """
450 Adds ip address to UserIpMap
450 Adds IP address to UserIpMap
451 451
452 452 :param user:
453 453 :param ip:
454 454 """
455 455 from kallithea.model import forms
456 456 form = forms.UserExtraIpForm()()
457 457 data = form.to_python(dict(ip=ip))
458 458 user = self._get_user(user)
459 459
460 460 obj = UserIpMap()
461 461 obj.user = user
462 462 obj.ip_addr = data['ip']
463 463 self.sa.add(obj)
464 464 return obj
465 465
466 466 def delete_extra_ip(self, user, ip_id):
467 467 """
468 Removes ip address from UserIpMap
468 Removes IP address from UserIpMap
469 469
470 470 :param user:
471 471 :param ip_id:
472 472 """
473 473 user = self._get_user(user)
474 474 obj = UserIpMap.query().get(ip_id)
475 475 if obj:
476 476 self.sa.delete(obj)
@@ -1,43 +1,43 b''
1 1 <div class="ips_wrap">
2 2 <table class="noborder">
3 3 %if c.user_ip_map:
4 4 %for ip in c.user_ip_map:
5 5 <tr>
6 6 <td><div class="ip">${ip.ip_addr}</div></td>
7 7 <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
8 8 <td>
9 9 ${h.form(url('edit_user_ips', id=c.user.user_id),method='delete')}
10 10 ${h.hidden('del_ip_id',ip.ip_id)}
11 11 ${h.hidden('default_user', 'True')}
12 12 <i class="icon-minus-circled" style="color:#FF4444"></i> ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
13 class_="action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
13 class_="action_button", onclick="return confirm('"+_('Confirm to delete this IP address: %s') % ip.ip_addr+"');")}
14 14 ${h.end_form()}
15 15 </td>
16 16 </tr>
17 17 %endfor
18 18 %else:
19 19 <tr><td><div class="ip">${_('All IP addresses are allowed.')}</div></td></tr>
20 20 %endif
21 21 </table>
22 22 </div>
23 23
24 24 ${h.form(url('edit_user_ips', id=c.user.user_id),method='put')}
25 25 <div class="form">
26 26 <!-- fields -->
27 27 <div class="fields">
28 28 <div class="field">
29 29 <div class="label">
30 <label for="new_ip">${_('New ip address')}:</label>
30 <label for="new_ip">${_('New IP address')}:</label>
31 31 </div>
32 32 <div class="input">
33 33 ${h.hidden('default_user', 'True')}
34 34 ${h.text('new_ip', class_='medium')}
35 35 </div>
36 36 </div>
37 37 <div class="buttons">
38 38 ${h.submit('save',_('Add'),class_="btn")}
39 39 ${h.reset('reset',_('Reset'),class_="btn")}
40 40 </div>
41 41 </div>
42 42 </div>
43 43 ${h.end_form()}
@@ -1,55 +1,55 b''
1 1 <div class="ips_wrap">
2 2 <table class="noborder">
3 3 %if c.default_user_ip_map and c.inherit_default_ips:
4 4 %for ip in c.default_user_ip_map:
5 5 <tr>
6 6 <td><div class="ip">${ip.ip_addr}</div></td>
7 7 <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
8 8 <td>${h.literal(_('Inherited from %s') % h.link_to('*default*',h.url('admin_permissions_ips')))}</td>
9 9 </tr>
10 10 %endfor
11 11 %endif
12 12
13 13 %if c.user_ip_map:
14 14 %for ip in c.user_ip_map:
15 15 <tr>
16 16 <td><div class="ip">${ip.ip_addr}</div></td>
17 17 <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
18 18 <td>
19 19 ${h.form(url('edit_user_ips', id=c.user.user_id),method='delete')}
20 20 ${h.hidden('del_ip_id',ip.ip_id)}
21 21 <i class="icon-minus-circled" style="color:#FF4444"></i>
22 22 ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
23 class_="action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
23 class_="action_button", onclick="return confirm('"+_('Confirm to delete this IP address: %s') % ip.ip_addr+"');")}
24 24 ${h.end_form()}
25 25 </td>
26 26 </tr>
27 27 %endfor
28 28 %endif
29 29 %if not c.default_user_ip_map and not c.user_ip_map:
30 30 <tr><td><div class="ip">${_('All IP addresses are allowed.')}</div></td></tr>
31 31 %endif
32 32 </table>
33 33 </div>
34 34
35 35 <div>
36 36 ${h.form(url('edit_user_ips', id=c.user.user_id),method='put')}
37 37 <div class="form">
38 38 <!-- fields -->
39 39 <div class="fields">
40 40 <div class="field">
41 41 <div class="label">
42 <label for="new_ip">${_('New ip address')}:</label>
42 <label for="new_ip">${_('New IP address')}:</label>
43 43 </div>
44 44 <div class="input">
45 45 ${h.text('new_ip', class_='medium')}
46 46 </div>
47 47 </div>
48 48 <div class="buttons">
49 49 ${h.submit('save',_('Add'),class_="btn")}
50 50 ${h.reset('reset',_('Reset'),class_="btn")}
51 51 </div>
52 52 </div>
53 53 </div>
54 54 ${h.end_form()}
55 55 </div>
General Comments 0
You need to be logged in to leave comments. Login now