##// END OF EJS Templates
Enable start_tls connection encryption.
"Lorenzo M. Catucci" -
r1290:74685a31 beta
parent child Browse files
Show More
@@ -1,124 +1,134
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.ldap_settings
3 rhodecode.controllers.admin.ldap_settings
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 ldap controller for RhodeCode
6 ldap controller for RhodeCode
7
7
8 :created_on: Nov 26, 2010
8 :created_on: Nov 26, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import logging
25 import logging
26 import formencode
26 import formencode
27 import traceback
27 import traceback
28
28
29 from formencode import htmlfill
29 from formencode import htmlfill
30
30
31 from pylons import request, response, session, tmpl_context as c, url
31 from pylons import request, response, session, tmpl_context as c, url
32 from pylons.controllers.util import abort, redirect
32 from pylons.controllers.util import abort, redirect
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34
34
35 from rhodecode.lib.base import BaseController, render
35 from rhodecode.lib.base import BaseController, render
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
38 from rhodecode.lib.auth_ldap import LdapImportError
38 from rhodecode.lib.auth_ldap import LdapImportError
39 from rhodecode.model.settings import SettingsModel
39 from rhodecode.model.settings import SettingsModel
40 from rhodecode.model.forms import LdapSettingsForm
40 from rhodecode.model.forms import LdapSettingsForm
41 from sqlalchemy.exc import DatabaseError
41 from sqlalchemy.exc import DatabaseError
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class LdapSettingsController(BaseController):
46 class LdapSettingsController(BaseController):
47
47
48 search_scope_choices = [('BASE', _('BASE'),),
48 search_scope_choices = [('BASE', _('BASE'),),
49 ('ONELEVEL', _('ONELEVEL'),),
49 ('ONELEVEL', _('ONELEVEL'),),
50 ('SUBTREE', _('SUBTREE'),),
50 ('SUBTREE', _('SUBTREE'),),
51 ]
51 ]
52 search_scope_default = 'SUBTREE'
52 search_scope_default = 'SUBTREE'
53
53
54 tls_reqcert_choices = [('NEVER', _('NEVER'),),
54 tls_reqcert_choices = [('NEVER', _('NEVER'),),
55 ('ALLOW', _('ALLOW'),),
55 ('ALLOW', _('ALLOW'),),
56 ('TRY', _('TRY'),),
56 ('TRY', _('TRY'),),
57 ('DEMAND', _('DEMAND'),),
57 ('DEMAND', _('DEMAND'),),
58 ('HARD', _('HARD'),),
58 ('HARD', _('HARD'),),
59 ]
59 ]
60 tls_reqcert_default = 'DEMAND'
60 tls_reqcert_default = 'DEMAND'
61
61
62 tls_kind_choices = [('PLAIN', _('No encryption'),),
63 ('LDAPS', _('LDAPS connection'),),
64 ('START_TLS', _('START_TLS on LDAP connection'),)
65 ]
66
67 tls_kind_default = 'PLAIN'
68
62 @LoginRequired()
69 @LoginRequired()
63 @HasPermissionAllDecorator('hg.admin')
70 @HasPermissionAllDecorator('hg.admin')
64 def __before__(self):
71 def __before__(self):
65 c.admin_user = session.get('admin_user')
72 c.admin_user = session.get('admin_user')
66 c.admin_username = session.get('admin_username')
73 c.admin_username = session.get('admin_username')
67 c.search_scope_choices = self.search_scope_choices
74 c.search_scope_choices = self.search_scope_choices
68 c.tls_reqcert_choices = self.tls_reqcert_choices
75 c.tls_reqcert_choices = self.tls_reqcert_choices
76 c.tls_kind_choices = self.tls_kind_choices
69 super(LdapSettingsController, self).__before__()
77 super(LdapSettingsController, self).__before__()
70
78
71 def index(self):
79 def index(self):
72 defaults = SettingsModel().get_ldap_settings()
80 defaults = SettingsModel().get_ldap_settings()
73 c.search_scope_cur = defaults.get('ldap_search_scope')
81 c.search_scope_cur = defaults.get('ldap_search_scope')
74 c.tls_reqcert_cur = defaults.get('ldap_tls_reqcert')
82 c.tls_reqcert_cur = defaults.get('ldap_tls_reqcert')
83 c.tls_kind_cur = defaults.get('ldap_tls_kind')
75
84
76 return htmlfill.render(
85 return htmlfill.render(
77 render('admin/ldap/ldap.html'),
86 render('admin/ldap/ldap.html'),
78 defaults=defaults,
87 defaults=defaults,
79 encoding="UTF-8",
88 encoding="UTF-8",
80 force_defaults=True,)
89 force_defaults=True,)
81
90
82 def ldap_settings(self):
91 def ldap_settings(self):
83 """POST ldap create and store ldap settings"""
92 """POST ldap create and store ldap settings"""
84
93
85 settings_model = SettingsModel()
94 settings_model = SettingsModel()
86 _form = LdapSettingsForm([x[0] for x in self.tls_reqcert_choices],
95 _form = LdapSettingsForm([x[0] for x in self.tls_reqcert_choices],
87 [x[0] for x in self.search_scope_choices])()
96 [x[0] for x in self.search_scope_choices],
97 [x[0] for x in self.tls_kind_choices])()
88
98
89 try:
99 try:
90 form_result = _form.to_python(dict(request.POST))
100 form_result = _form.to_python(dict(request.POST))
91 try:
101 try:
92
102
93 for k, v in form_result.items():
103 for k, v in form_result.items():
94 if k.startswith('ldap_'):
104 if k.startswith('ldap_'):
95 setting = settings_model.get(k)
105 setting = settings_model.get(k)
96 setting.app_settings_value = v
106 setting.app_settings_value = v
97 self.sa.add(setting)
107 self.sa.add(setting)
98
108
99 self.sa.commit()
109 self.sa.commit()
100 h.flash(_('Ldap settings updated successfully'),
110 h.flash(_('Ldap settings updated successfully'),
101 category='success')
111 category='success')
102 except (DatabaseError,):
112 except (DatabaseError,):
103 raise
113 raise
104 except LdapImportError:
114 except LdapImportError:
105 h.flash(_('Unable to activate ldap. The "python-ldap" library '
115 h.flash(_('Unable to activate ldap. The "python-ldap" library '
106 'is missing.'), category='warning')
116 'is missing.'), category='warning')
107
117
108 except formencode.Invalid, errors:
118 except formencode.Invalid, errors:
109
119
110 c.search_scope_cur = self.search_scope_default
120 c.search_scope_cur = self.search_scope_default
111 c.tls_reqcert_cur = self.search_scope_default
121 c.tls_reqcert_cur = self.search_scope_default
112
122
113 return htmlfill.render(
123 return htmlfill.render(
114 render('admin/ldap/ldap.html'),
124 render('admin/ldap/ldap.html'),
115 defaults=errors.value,
125 defaults=errors.value,
116 errors=errors.error_dict or {},
126 errors=errors.error_dict or {},
117 prefix_error=False,
127 prefix_error=False,
118 encoding="UTF-8")
128 encoding="UTF-8")
119 except Exception:
129 except Exception:
120 log.error(traceback.format_exc())
130 log.error(traceback.format_exc())
121 h.flash(_('error occurred during update of ldap settings'),
131 h.flash(_('error occurred during update of ldap settings'),
122 category='error')
132 category='error')
123
133
124 return redirect(url('ldap_home'))
134 return redirect(url('ldap_home'))
@@ -1,602 +1,602
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.auth
3 rhodecode.lib.auth
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 authentication and permission libraries
6 authentication and permission libraries
7
7
8 :created_on: Apr 4, 2010
8 :created_on: Apr 4, 2010
9 :copyright: (c) 2010 by marcink.
9 :copyright: (c) 2010 by marcink.
10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
11 """
11 """
12 # This program is free software: you can redistribute it and/or modify
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
15 # (at your option) any later version.
16 #
16 #
17 # This program is distributed in the hope that it will be useful,
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
20 # GNU General Public License for more details.
21 #
21 #
22 # You should have received a copy of the GNU General Public License
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24
24
25 import random
25 import random
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import hashlib
28 import hashlib
29
29
30 from tempfile import _RandomNameSequence
30 from tempfile import _RandomNameSequence
31 from decorator import decorator
31 from decorator import decorator
32
32
33 from pylons import config, session, url, request
33 from pylons import config, session, url, request
34 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import abort, redirect
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
37 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
38
38
39 if __platform__ in PLATFORM_WIN:
39 if __platform__ in PLATFORM_WIN:
40 from hashlib import sha256
40 from hashlib import sha256
41 if __platform__ in PLATFORM_OTHERS:
41 if __platform__ in PLATFORM_OTHERS:
42 import bcrypt
42 import bcrypt
43
43
44 from rhodecode.lib import str2bool
44 from rhodecode.lib import str2bool
45 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
45 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
46 from rhodecode.lib.utils import get_repo_slug
46 from rhodecode.lib.utils import get_repo_slug
47 from rhodecode.lib.auth_ldap import AuthLdap
47 from rhodecode.lib.auth_ldap import AuthLdap
48
48
49 from rhodecode.model import meta
49 from rhodecode.model import meta
50 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
51 from rhodecode.model.db import Permission
51 from rhodecode.model.db import Permission
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 class PasswordGenerator(object):
56 class PasswordGenerator(object):
57 """This is a simple class for generating password from
57 """This is a simple class for generating password from
58 different sets of characters
58 different sets of characters
59 usage:
59 usage:
60 passwd_gen = PasswordGenerator()
60 passwd_gen = PasswordGenerator()
61 #print 8-letter password containing only big and small letters
61 #print 8-letter password containing only big and small letters
62 of alphabet
62 of alphabet
63 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
63 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
64 """
64 """
65 ALPHABETS_NUM = r'''1234567890'''
65 ALPHABETS_NUM = r'''1234567890'''
66 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
66 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
67 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
67 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
68 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
68 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
69 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
69 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
70 + ALPHABETS_NUM + ALPHABETS_SPECIAL
70 + ALPHABETS_NUM + ALPHABETS_SPECIAL
71 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
71 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
72 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
72 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
73 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
73 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
74 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
74 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
75
75
76 def __init__(self, passwd=''):
76 def __init__(self, passwd=''):
77 self.passwd = passwd
77 self.passwd = passwd
78
78
79 def gen_password(self, len, type):
79 def gen_password(self, len, type):
80 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
80 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
81 return self.passwd
81 return self.passwd
82
82
83
83
84 class RhodeCodeCrypto(object):
84 class RhodeCodeCrypto(object):
85
85
86 @classmethod
86 @classmethod
87 def hash_string(cls, str_):
87 def hash_string(cls, str_):
88 """
88 """
89 Cryptographic function used for password hashing based on pybcrypt
89 Cryptographic function used for password hashing based on pybcrypt
90 or pycrypto in windows
90 or pycrypto in windows
91
91
92 :param password: password to hash
92 :param password: password to hash
93 """
93 """
94 if __platform__ in PLATFORM_WIN:
94 if __platform__ in PLATFORM_WIN:
95 return sha256(str_).hexdigest()
95 return sha256(str_).hexdigest()
96 elif __platform__ in PLATFORM_OTHERS:
96 elif __platform__ in PLATFORM_OTHERS:
97 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
97 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
98 else:
98 else:
99 raise Exception('Unknown or unsupported platform %s' \
99 raise Exception('Unknown or unsupported platform %s' \
100 % __platform__)
100 % __platform__)
101
101
102 @classmethod
102 @classmethod
103 def hash_check(cls, password, hashed):
103 def hash_check(cls, password, hashed):
104 """
104 """
105 Checks matching password with it's hashed value, runs different
105 Checks matching password with it's hashed value, runs different
106 implementation based on platform it runs on
106 implementation based on platform it runs on
107
107
108 :param password: password
108 :param password: password
109 :param hashed: password in hashed form
109 :param hashed: password in hashed form
110 """
110 """
111
111
112 if __platform__ in PLATFORM_WIN:
112 if __platform__ in PLATFORM_WIN:
113 return sha256(password).hexdigest() == hashed
113 return sha256(password).hexdigest() == hashed
114 elif __platform__ in PLATFORM_OTHERS:
114 elif __platform__ in PLATFORM_OTHERS:
115 return bcrypt.hashpw(password, hashed) == hashed
115 return bcrypt.hashpw(password, hashed) == hashed
116 else:
116 else:
117 raise Exception('Unknown or unsupported platform %s' \
117 raise Exception('Unknown or unsupported platform %s' \
118 % __platform__)
118 % __platform__)
119
119
120
120
121 def get_crypt_password(password):
121 def get_crypt_password(password):
122 return RhodeCodeCrypto.hash_string(password)
122 return RhodeCodeCrypto.hash_string(password)
123
123
124
124
125 def check_password(password, hashed):
125 def check_password(password, hashed):
126 return RhodeCodeCrypto.hash_check(password, hashed)
126 return RhodeCodeCrypto.hash_check(password, hashed)
127
127
128
128
129 def generate_api_key(username, salt=None):
129 def generate_api_key(username, salt=None):
130 if salt is None:
130 if salt is None:
131 salt = _RandomNameSequence().next()
131 salt = _RandomNameSequence().next()
132
132
133 return hashlib.sha1(username + salt).hexdigest()
133 return hashlib.sha1(username + salt).hexdigest()
134
134
135
135
136 def authfunc(environ, username, password):
136 def authfunc(environ, username, password):
137 """Dummy authentication function used in Mercurial/Git/ and access control,
137 """Dummy authentication function used in Mercurial/Git/ and access control,
138
138
139 :param environ: needed only for using in Basic auth
139 :param environ: needed only for using in Basic auth
140 """
140 """
141 return authenticate(username, password)
141 return authenticate(username, password)
142
142
143
143
144 def authenticate(username, password):
144 def authenticate(username, password):
145 """Authentication function used for access control,
145 """Authentication function used for access control,
146 firstly checks for db authentication then if ldap is enabled for ldap
146 firstly checks for db authentication then if ldap is enabled for ldap
147 authentication, also creates ldap user if not in database
147 authentication, also creates ldap user if not in database
148
148
149 :param username: username
149 :param username: username
150 :param password: password
150 :param password: password
151 """
151 """
152 user_model = UserModel()
152 user_model = UserModel()
153 user = user_model.get_by_username(username, cache=False)
153 user = user_model.get_by_username(username, cache=False)
154
154
155 log.debug('Authenticating user using RhodeCode account')
155 log.debug('Authenticating user using RhodeCode account')
156 if user is not None and not user.ldap_dn:
156 if user is not None and not user.ldap_dn:
157 if user.active:
157 if user.active:
158 if user.username == 'default' and user.active:
158 if user.username == 'default' and user.active:
159 log.info('user %s authenticated correctly as anonymous user',
159 log.info('user %s authenticated correctly as anonymous user',
160 username)
160 username)
161 return True
161 return True
162
162
163 elif user.username == username and check_password(password,
163 elif user.username == username and check_password(password,
164 user.password):
164 user.password):
165 log.info('user %s authenticated correctly', username)
165 log.info('user %s authenticated correctly', username)
166 return True
166 return True
167 else:
167 else:
168 log.warning('user %s is disabled', username)
168 log.warning('user %s is disabled', username)
169
169
170 else:
170 else:
171 log.debug('Regular authentication failed')
171 log.debug('Regular authentication failed')
172 user_obj = user_model.get_by_username(username, cache=False,
172 user_obj = user_model.get_by_username(username, cache=False,
173 case_insensitive=True)
173 case_insensitive=True)
174
174
175 if user_obj is not None and not user_obj.ldap_dn:
175 if user_obj is not None and not user_obj.ldap_dn:
176 log.debug('this user already exists as non ldap')
176 log.debug('this user already exists as non ldap')
177 return False
177 return False
178
178
179 from rhodecode.model.settings import SettingsModel
179 from rhodecode.model.settings import SettingsModel
180 ldap_settings = SettingsModel().get_ldap_settings()
180 ldap_settings = SettingsModel().get_ldap_settings()
181
181
182 #======================================================================
182 #======================================================================
183 # FALLBACK TO LDAP AUTH IF ENABLE
183 # FALLBACK TO LDAP AUTH IF ENABLE
184 #======================================================================
184 #======================================================================
185 if str2bool(ldap_settings.get('ldap_active')):
185 if str2bool(ldap_settings.get('ldap_active')):
186 log.debug("Authenticating user using ldap")
186 log.debug("Authenticating user using ldap")
187 kwargs = {
187 kwargs = {
188 'server': ldap_settings.get('ldap_host', ''),
188 'server': ldap_settings.get('ldap_host', ''),
189 'base_dn': ldap_settings.get('ldap_base_dn', ''),
189 'base_dn': ldap_settings.get('ldap_base_dn', ''),
190 'port': ldap_settings.get('ldap_port'),
190 'port': ldap_settings.get('ldap_port'),
191 'bind_dn': ldap_settings.get('ldap_dn_user'),
191 'bind_dn': ldap_settings.get('ldap_dn_user'),
192 'bind_pass': ldap_settings.get('ldap_dn_pass'),
192 'bind_pass': ldap_settings.get('ldap_dn_pass'),
193 'use_ldaps': str2bool(ldap_settings.get('ldap_ldaps')),
193 'tls_kind': ldap_settings.get('ldap_tls_kind'),
194 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
194 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
195 'ldap_filter': ldap_settings.get('ldap_filter'),
195 'ldap_filter': ldap_settings.get('ldap_filter'),
196 'search_scope': ldap_settings.get('ldap_search_scope'),
196 'search_scope': ldap_settings.get('ldap_search_scope'),
197 'attr_login': ldap_settings.get('ldap_attr_login'),
197 'attr_login': ldap_settings.get('ldap_attr_login'),
198 'ldap_version': 3,
198 'ldap_version': 3,
199 }
199 }
200 log.debug('Checking for ldap authentication')
200 log.debug('Checking for ldap authentication')
201 try:
201 try:
202 aldap = AuthLdap(**kwargs)
202 aldap = AuthLdap(**kwargs)
203 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
203 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
204 password)
204 password)
205 log.debug('Got ldap DN response %s', user_dn)
205 log.debug('Got ldap DN response %s', user_dn)
206
206
207 user_attrs = {
207 user_attrs = {
208 'name': ldap_attrs.get(ldap_settings\
208 'name': ldap_attrs.get(ldap_settings\
209 .get('ldap_attr_firstname'), [''])[0],
209 .get('ldap_attr_firstname'), [''])[0],
210 'lastname': ldap_attrs.get(ldap_settings\
210 'lastname': ldap_attrs.get(ldap_settings\
211 .get('ldap_attr_lastname'),[''])[0],
211 .get('ldap_attr_lastname'),[''])[0],
212 'email': ldap_attrs.get(ldap_settings\
212 'email': ldap_attrs.get(ldap_settings\
213 .get('ldap_attr_email'), [''])[0],
213 .get('ldap_attr_email'), [''])[0],
214 }
214 }
215
215
216 if user_model.create_ldap(username, password, user_dn,
216 if user_model.create_ldap(username, password, user_dn,
217 user_attrs):
217 user_attrs):
218 log.info('created new ldap user %s', username)
218 log.info('created new ldap user %s', username)
219
219
220 return True
220 return True
221 except (LdapUsernameError, LdapPasswordError,):
221 except (LdapUsernameError, LdapPasswordError,):
222 pass
222 pass
223 except (Exception,):
223 except (Exception,):
224 log.error(traceback.format_exc())
224 log.error(traceback.format_exc())
225 pass
225 pass
226 return False
226 return False
227
227
228
228
229 class AuthUser(object):
229 class AuthUser(object):
230 """
230 """
231 A simple object that handles all attributes of user in RhodeCode
231 A simple object that handles all attributes of user in RhodeCode
232
232
233 It does lookup based on API key,given user, or user present in session
233 It does lookup based on API key,given user, or user present in session
234 Then it fills all required information for such user. It also checks if
234 Then it fills all required information for such user. It also checks if
235 anonymous access is enabled and if so, it returns default user as logged
235 anonymous access is enabled and if so, it returns default user as logged
236 in
236 in
237 """
237 """
238
238
239 def __init__(self, user_id=None, api_key=None):
239 def __init__(self, user_id=None, api_key=None):
240
240
241 self.user_id = user_id
241 self.user_id = user_id
242 self.api_key = None
242 self.api_key = None
243
243
244 self.username = 'None'
244 self.username = 'None'
245 self.name = ''
245 self.name = ''
246 self.lastname = ''
246 self.lastname = ''
247 self.email = ''
247 self.email = ''
248 self.is_authenticated = False
248 self.is_authenticated = False
249 self.admin = False
249 self.admin = False
250 self.permissions = {}
250 self.permissions = {}
251 self._api_key = api_key
251 self._api_key = api_key
252 self.propagate_data()
252 self.propagate_data()
253
253
254 def propagate_data(self):
254 def propagate_data(self):
255 user_model = UserModel()
255 user_model = UserModel()
256 self.anonymous_user = user_model.get_by_username('default', cache=True)
256 self.anonymous_user = user_model.get_by_username('default', cache=True)
257 if self._api_key and self._api_key != self.anonymous_user.api_key:
257 if self._api_key and self._api_key != self.anonymous_user.api_key:
258 #try go get user by api key
258 #try go get user by api key
259 log.debug('Auth User lookup by API KEY %s', self._api_key)
259 log.debug('Auth User lookup by API KEY %s', self._api_key)
260 user_model.fill_data(self, api_key=self._api_key)
260 user_model.fill_data(self, api_key=self._api_key)
261 else:
261 else:
262 log.debug('Auth User lookup by USER ID %s', self.user_id)
262 log.debug('Auth User lookup by USER ID %s', self.user_id)
263 if self.user_id is not None \
263 if self.user_id is not None \
264 and self.user_id != self.anonymous_user.user_id:
264 and self.user_id != self.anonymous_user.user_id:
265 user_model.fill_data(self, user_id=self.user_id)
265 user_model.fill_data(self, user_id=self.user_id)
266 else:
266 else:
267 if self.anonymous_user.active is True:
267 if self.anonymous_user.active is True:
268 user_model.fill_data(self,
268 user_model.fill_data(self,
269 user_id=self.anonymous_user.user_id)
269 user_id=self.anonymous_user.user_id)
270 #then we set this user is logged in
270 #then we set this user is logged in
271 self.is_authenticated = True
271 self.is_authenticated = True
272 else:
272 else:
273 self.is_authenticated = False
273 self.is_authenticated = False
274
274
275 log.debug('Auth User is now %s', self)
275 log.debug('Auth User is now %s', self)
276 user_model.fill_perms(self)
276 user_model.fill_perms(self)
277
277
278 @property
278 @property
279 def is_admin(self):
279 def is_admin(self):
280 return self.admin
280 return self.admin
281
281
282 def __repr__(self):
282 def __repr__(self):
283 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
283 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
284 self.is_authenticated)
284 self.is_authenticated)
285
285
286 def set_authenticated(self, authenticated=True):
286 def set_authenticated(self, authenticated=True):
287
287
288 if self.user_id != self.anonymous_user.user_id:
288 if self.user_id != self.anonymous_user.user_id:
289 self.is_authenticated = authenticated
289 self.is_authenticated = authenticated
290
290
291
291
292 def set_available_permissions(config):
292 def set_available_permissions(config):
293 """This function will propagate pylons globals with all available defined
293 """This function will propagate pylons globals with all available defined
294 permission given in db. We don't want to check each time from db for new
294 permission given in db. We don't want to check each time from db for new
295 permissions since adding a new permission also requires application restart
295 permissions since adding a new permission also requires application restart
296 ie. to decorate new views with the newly created permission
296 ie. to decorate new views with the newly created permission
297
297
298 :param config: current pylons config instance
298 :param config: current pylons config instance
299
299
300 """
300 """
301 log.info('getting information about all available permissions')
301 log.info('getting information about all available permissions')
302 try:
302 try:
303 sa = meta.Session()
303 sa = meta.Session()
304 all_perms = sa.query(Permission).all()
304 all_perms = sa.query(Permission).all()
305 except:
305 except:
306 pass
306 pass
307 finally:
307 finally:
308 meta.Session.remove()
308 meta.Session.remove()
309
309
310 config['available_permissions'] = [x.permission_name for x in all_perms]
310 config['available_permissions'] = [x.permission_name for x in all_perms]
311
311
312
312
313 #==============================================================================
313 #==============================================================================
314 # CHECK DECORATORS
314 # CHECK DECORATORS
315 #==============================================================================
315 #==============================================================================
316 class LoginRequired(object):
316 class LoginRequired(object):
317 """
317 """
318 Must be logged in to execute this function else
318 Must be logged in to execute this function else
319 redirect to login page
319 redirect to login page
320
320
321 :param api_access: if enabled this checks only for valid auth token
321 :param api_access: if enabled this checks only for valid auth token
322 and grants access based on valid token
322 and grants access based on valid token
323 """
323 """
324
324
325 def __init__(self, api_access=False):
325 def __init__(self, api_access=False):
326 self.api_access = api_access
326 self.api_access = api_access
327
327
328 def __call__(self, func):
328 def __call__(self, func):
329 return decorator(self.__wrapper, func)
329 return decorator(self.__wrapper, func)
330
330
331 def __wrapper(self, func, *fargs, **fkwargs):
331 def __wrapper(self, func, *fargs, **fkwargs):
332 cls = fargs[0]
332 cls = fargs[0]
333 user = cls.rhodecode_user
333 user = cls.rhodecode_user
334
334
335 api_access_ok = False
335 api_access_ok = False
336 if self.api_access:
336 if self.api_access:
337 log.debug('Checking API KEY access for %s', cls)
337 log.debug('Checking API KEY access for %s', cls)
338 if user.api_key == request.GET.get('api_key'):
338 if user.api_key == request.GET.get('api_key'):
339 api_access_ok = True
339 api_access_ok = True
340 else:
340 else:
341 log.debug("API KEY token not valid")
341 log.debug("API KEY token not valid")
342
342
343 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
343 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
344 if user.is_authenticated or api_access_ok:
344 if user.is_authenticated or api_access_ok:
345 log.debug('user %s is authenticated', user.username)
345 log.debug('user %s is authenticated', user.username)
346 return func(*fargs, **fkwargs)
346 return func(*fargs, **fkwargs)
347 else:
347 else:
348 log.warn('user %s NOT authenticated', user.username)
348 log.warn('user %s NOT authenticated', user.username)
349 p = url.current()
349 p = url.current()
350
350
351 log.debug('redirecting to login page with %s', p)
351 log.debug('redirecting to login page with %s', p)
352 return redirect(url('login_home', came_from=p))
352 return redirect(url('login_home', came_from=p))
353
353
354
354
355 class NotAnonymous(object):
355 class NotAnonymous(object):
356 """Must be logged in to execute this function else
356 """Must be logged in to execute this function else
357 redirect to login page"""
357 redirect to login page"""
358
358
359 def __call__(self, func):
359 def __call__(self, func):
360 return decorator(self.__wrapper, func)
360 return decorator(self.__wrapper, func)
361
361
362 def __wrapper(self, func, *fargs, **fkwargs):
362 def __wrapper(self, func, *fargs, **fkwargs):
363 cls = fargs[0]
363 cls = fargs[0]
364 self.user = cls.rhodecode_user
364 self.user = cls.rhodecode_user
365
365
366 log.debug('Checking if user is not anonymous @%s', cls)
366 log.debug('Checking if user is not anonymous @%s', cls)
367
367
368 anonymous = self.user.username == 'default'
368 anonymous = self.user.username == 'default'
369
369
370 if anonymous:
370 if anonymous:
371 p = ''
371 p = ''
372 if request.environ.get('SCRIPT_NAME') != '/':
372 if request.environ.get('SCRIPT_NAME') != '/':
373 p += request.environ.get('SCRIPT_NAME')
373 p += request.environ.get('SCRIPT_NAME')
374
374
375 p += request.environ.get('PATH_INFO')
375 p += request.environ.get('PATH_INFO')
376 if request.environ.get('QUERY_STRING'):
376 if request.environ.get('QUERY_STRING'):
377 p += '?' + request.environ.get('QUERY_STRING')
377 p += '?' + request.environ.get('QUERY_STRING')
378
378
379 import rhodecode.lib.helpers as h
379 import rhodecode.lib.helpers as h
380 h.flash(_('You need to be a registered user to '
380 h.flash(_('You need to be a registered user to '
381 'perform this action'),
381 'perform this action'),
382 category='warning')
382 category='warning')
383 return redirect(url('login_home', came_from=p))
383 return redirect(url('login_home', came_from=p))
384 else:
384 else:
385 return func(*fargs, **fkwargs)
385 return func(*fargs, **fkwargs)
386
386
387
387
388 class PermsDecorator(object):
388 class PermsDecorator(object):
389 """Base class for controller decorators"""
389 """Base class for controller decorators"""
390
390
391 def __init__(self, *required_perms):
391 def __init__(self, *required_perms):
392 available_perms = config['available_permissions']
392 available_perms = config['available_permissions']
393 for perm in required_perms:
393 for perm in required_perms:
394 if perm not in available_perms:
394 if perm not in available_perms:
395 raise Exception("'%s' permission is not defined" % perm)
395 raise Exception("'%s' permission is not defined" % perm)
396 self.required_perms = set(required_perms)
396 self.required_perms = set(required_perms)
397 self.user_perms = None
397 self.user_perms = None
398
398
399 def __call__(self, func):
399 def __call__(self, func):
400 return decorator(self.__wrapper, func)
400 return decorator(self.__wrapper, func)
401
401
402 def __wrapper(self, func, *fargs, **fkwargs):
402 def __wrapper(self, func, *fargs, **fkwargs):
403 cls = fargs[0]
403 cls = fargs[0]
404 self.user = cls.rhodecode_user
404 self.user = cls.rhodecode_user
405 self.user_perms = self.user.permissions
405 self.user_perms = self.user.permissions
406 log.debug('checking %s permissions %s for %s %s',
406 log.debug('checking %s permissions %s for %s %s',
407 self.__class__.__name__, self.required_perms, cls,
407 self.__class__.__name__, self.required_perms, cls,
408 self.user)
408 self.user)
409
409
410 if self.check_permissions():
410 if self.check_permissions():
411 log.debug('Permission granted for %s %s', cls, self.user)
411 log.debug('Permission granted for %s %s', cls, self.user)
412 return func(*fargs, **fkwargs)
412 return func(*fargs, **fkwargs)
413
413
414 else:
414 else:
415 log.warning('Permission denied for %s %s', cls, self.user)
415 log.warning('Permission denied for %s %s', cls, self.user)
416 #redirect with forbidden ret code
416 #redirect with forbidden ret code
417 return abort(403)
417 return abort(403)
418
418
419 def check_permissions(self):
419 def check_permissions(self):
420 """Dummy function for overriding"""
420 """Dummy function for overriding"""
421 raise Exception('You have to write this function in child class')
421 raise Exception('You have to write this function in child class')
422
422
423
423
424 class HasPermissionAllDecorator(PermsDecorator):
424 class HasPermissionAllDecorator(PermsDecorator):
425 """Checks for access permission for all given predicates. All of them
425 """Checks for access permission for all given predicates. All of them
426 have to be meet in order to fulfill the request
426 have to be meet in order to fulfill the request
427 """
427 """
428
428
429 def check_permissions(self):
429 def check_permissions(self):
430 if self.required_perms.issubset(self.user_perms.get('global')):
430 if self.required_perms.issubset(self.user_perms.get('global')):
431 return True
431 return True
432 return False
432 return False
433
433
434
434
435 class HasPermissionAnyDecorator(PermsDecorator):
435 class HasPermissionAnyDecorator(PermsDecorator):
436 """Checks for access permission for any of given predicates. In order to
436 """Checks for access permission for any of given predicates. In order to
437 fulfill the request any of predicates must be meet
437 fulfill the request any of predicates must be meet
438 """
438 """
439
439
440 def check_permissions(self):
440 def check_permissions(self):
441 if self.required_perms.intersection(self.user_perms.get('global')):
441 if self.required_perms.intersection(self.user_perms.get('global')):
442 return True
442 return True
443 return False
443 return False
444
444
445
445
446 class HasRepoPermissionAllDecorator(PermsDecorator):
446 class HasRepoPermissionAllDecorator(PermsDecorator):
447 """Checks for access permission for all given predicates for specific
447 """Checks for access permission for all given predicates for specific
448 repository. All of them have to be meet in order to fulfill the request
448 repository. All of them have to be meet in order to fulfill the request
449 """
449 """
450
450
451 def check_permissions(self):
451 def check_permissions(self):
452 repo_name = get_repo_slug(request)
452 repo_name = get_repo_slug(request)
453 try:
453 try:
454 user_perms = set([self.user_perms['repositories'][repo_name]])
454 user_perms = set([self.user_perms['repositories'][repo_name]])
455 except KeyError:
455 except KeyError:
456 return False
456 return False
457 if self.required_perms.issubset(user_perms):
457 if self.required_perms.issubset(user_perms):
458 return True
458 return True
459 return False
459 return False
460
460
461
461
462 class HasRepoPermissionAnyDecorator(PermsDecorator):
462 class HasRepoPermissionAnyDecorator(PermsDecorator):
463 """Checks for access permission for any of given predicates for specific
463 """Checks for access permission for any of given predicates for specific
464 repository. In order to fulfill the request any of predicates must be meet
464 repository. In order to fulfill the request any of predicates must be meet
465 """
465 """
466
466
467 def check_permissions(self):
467 def check_permissions(self):
468 repo_name = get_repo_slug(request)
468 repo_name = get_repo_slug(request)
469
469
470 try:
470 try:
471 user_perms = set([self.user_perms['repositories'][repo_name]])
471 user_perms = set([self.user_perms['repositories'][repo_name]])
472 except KeyError:
472 except KeyError:
473 return False
473 return False
474 if self.required_perms.intersection(user_perms):
474 if self.required_perms.intersection(user_perms):
475 return True
475 return True
476 return False
476 return False
477
477
478
478
479 #==============================================================================
479 #==============================================================================
480 # CHECK FUNCTIONS
480 # CHECK FUNCTIONS
481 #==============================================================================
481 #==============================================================================
482 class PermsFunction(object):
482 class PermsFunction(object):
483 """Base function for other check functions"""
483 """Base function for other check functions"""
484
484
485 def __init__(self, *perms):
485 def __init__(self, *perms):
486 available_perms = config['available_permissions']
486 available_perms = config['available_permissions']
487
487
488 for perm in perms:
488 for perm in perms:
489 if perm not in available_perms:
489 if perm not in available_perms:
490 raise Exception("'%s' permission in not defined" % perm)
490 raise Exception("'%s' permission in not defined" % perm)
491 self.required_perms = set(perms)
491 self.required_perms = set(perms)
492 self.user_perms = None
492 self.user_perms = None
493 self.granted_for = ''
493 self.granted_for = ''
494 self.repo_name = None
494 self.repo_name = None
495
495
496 def __call__(self, check_Location=''):
496 def __call__(self, check_Location=''):
497 user = session.get('rhodecode_user', False)
497 user = session.get('rhodecode_user', False)
498 if not user:
498 if not user:
499 return False
499 return False
500 self.user_perms = user.permissions
500 self.user_perms = user.permissions
501 self.granted_for = user
501 self.granted_for = user
502 log.debug('checking %s %s %s', self.__class__.__name__,
502 log.debug('checking %s %s %s', self.__class__.__name__,
503 self.required_perms, user)
503 self.required_perms, user)
504
504
505 if self.check_permissions():
505 if self.check_permissions():
506 log.debug('Permission granted %s @ %s', self.granted_for,
506 log.debug('Permission granted %s @ %s', self.granted_for,
507 check_Location or 'unspecified location')
507 check_Location or 'unspecified location')
508 return True
508 return True
509
509
510 else:
510 else:
511 log.warning('Permission denied for %s @ %s', self.granted_for,
511 log.warning('Permission denied for %s @ %s', self.granted_for,
512 check_Location or 'unspecified location')
512 check_Location or 'unspecified location')
513 return False
513 return False
514
514
515 def check_permissions(self):
515 def check_permissions(self):
516 """Dummy function for overriding"""
516 """Dummy function for overriding"""
517 raise Exception('You have to write this function in child class')
517 raise Exception('You have to write this function in child class')
518
518
519
519
520 class HasPermissionAll(PermsFunction):
520 class HasPermissionAll(PermsFunction):
521 def check_permissions(self):
521 def check_permissions(self):
522 if self.required_perms.issubset(self.user_perms.get('global')):
522 if self.required_perms.issubset(self.user_perms.get('global')):
523 return True
523 return True
524 return False
524 return False
525
525
526
526
527 class HasPermissionAny(PermsFunction):
527 class HasPermissionAny(PermsFunction):
528 def check_permissions(self):
528 def check_permissions(self):
529 if self.required_perms.intersection(self.user_perms.get('global')):
529 if self.required_perms.intersection(self.user_perms.get('global')):
530 return True
530 return True
531 return False
531 return False
532
532
533
533
534 class HasRepoPermissionAll(PermsFunction):
534 class HasRepoPermissionAll(PermsFunction):
535
535
536 def __call__(self, repo_name=None, check_Location=''):
536 def __call__(self, repo_name=None, check_Location=''):
537 self.repo_name = repo_name
537 self.repo_name = repo_name
538 return super(HasRepoPermissionAll, self).__call__(check_Location)
538 return super(HasRepoPermissionAll, self).__call__(check_Location)
539
539
540 def check_permissions(self):
540 def check_permissions(self):
541 if not self.repo_name:
541 if not self.repo_name:
542 self.repo_name = get_repo_slug(request)
542 self.repo_name = get_repo_slug(request)
543
543
544 try:
544 try:
545 self.user_perms = set([self.user_perms['reposit'
545 self.user_perms = set([self.user_perms['reposit'
546 'ories'][self.repo_name]])
546 'ories'][self.repo_name]])
547 except KeyError:
547 except KeyError:
548 return False
548 return False
549 self.granted_for = self.repo_name
549 self.granted_for = self.repo_name
550 if self.required_perms.issubset(self.user_perms):
550 if self.required_perms.issubset(self.user_perms):
551 return True
551 return True
552 return False
552 return False
553
553
554
554
555 class HasRepoPermissionAny(PermsFunction):
555 class HasRepoPermissionAny(PermsFunction):
556
556
557 def __call__(self, repo_name=None, check_Location=''):
557 def __call__(self, repo_name=None, check_Location=''):
558 self.repo_name = repo_name
558 self.repo_name = repo_name
559 return super(HasRepoPermissionAny, self).__call__(check_Location)
559 return super(HasRepoPermissionAny, self).__call__(check_Location)
560
560
561 def check_permissions(self):
561 def check_permissions(self):
562 if not self.repo_name:
562 if not self.repo_name:
563 self.repo_name = get_repo_slug(request)
563 self.repo_name = get_repo_slug(request)
564
564
565 try:
565 try:
566 self.user_perms = set([self.user_perms['reposi'
566 self.user_perms = set([self.user_perms['reposi'
567 'tories'][self.repo_name]])
567 'tories'][self.repo_name]])
568 except KeyError:
568 except KeyError:
569 return False
569 return False
570 self.granted_for = self.repo_name
570 self.granted_for = self.repo_name
571 if self.required_perms.intersection(self.user_perms):
571 if self.required_perms.intersection(self.user_perms):
572 return True
572 return True
573 return False
573 return False
574
574
575
575
576 #==============================================================================
576 #==============================================================================
577 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
577 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
578 #==============================================================================
578 #==============================================================================
579 class HasPermissionAnyMiddleware(object):
579 class HasPermissionAnyMiddleware(object):
580 def __init__(self, *perms):
580 def __init__(self, *perms):
581 self.required_perms = set(perms)
581 self.required_perms = set(perms)
582
582
583 def __call__(self, user, repo_name):
583 def __call__(self, user, repo_name):
584 usr = AuthUser(user.user_id)
584 usr = AuthUser(user.user_id)
585 try:
585 try:
586 self.user_perms = set([usr.permissions['repositories'][repo_name]])
586 self.user_perms = set([usr.permissions['repositories'][repo_name]])
587 except:
587 except:
588 self.user_perms = set()
588 self.user_perms = set()
589 self.granted_for = ''
589 self.granted_for = ''
590 self.username = user.username
590 self.username = user.username
591 self.repo_name = repo_name
591 self.repo_name = repo_name
592 return self.check_permissions()
592 return self.check_permissions()
593
593
594 def check_permissions(self):
594 def check_permissions(self):
595 log.debug('checking mercurial protocol '
595 log.debug('checking mercurial protocol '
596 'permissions %s for user:%s repository:%s', self.user_perms,
596 'permissions %s for user:%s repository:%s', self.user_perms,
597 self.username, self.repo_name)
597 self.username, self.repo_name)
598 if self.required_perms.intersection(self.user_perms):
598 if self.required_perms.intersection(self.user_perms):
599 log.debug('permission granted')
599 log.debug('permission granted')
600 return True
600 return True
601 log.debug('permission denied')
601 log.debug('permission denied')
602 return False
602 return False
@@ -1,129 +1,135
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # ldap authentication lib
3 # ldap authentication lib
4 # Copyright (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software: you can redistribute it and/or modify
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
9 # (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 """
18 """
19 Created on Nov 17, 2010
19 Created on Nov 17, 2010
20
20
21 @author: marcink
21 @author: marcink
22 """
22 """
23
23
24 from rhodecode.lib.exceptions import *
24 from rhodecode.lib.exceptions import *
25 import logging
25 import logging
26
26
27 log = logging.getLogger(__name__)
27 log = logging.getLogger(__name__)
28
28
29 try:
29 try:
30 import ldap
30 import ldap
31 except ImportError:
31 except ImportError:
32 pass
32 pass
33
33
34 class AuthLdap(object):
34 class AuthLdap(object):
35
35
36 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
36 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
37 use_ldaps=False, tls_reqcert='DEMAND', ldap_version=3,
37 tls_kind = 'PLAIN', tls_reqcert='DEMAND', ldap_version=3,
38 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
38 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
39 search_scope='SUBTREE',
39 search_scope='SUBTREE',
40 attr_login='uid'):
40 attr_login='uid'):
41 self.ldap_version = ldap_version
41 self.ldap_version = ldap_version
42 if use_ldaps:
42 ldap_server_type = 'ldap'
43
44 self.TLS_KIND = tls_kind
45
46 if self.TLS_KIND == 'LDAPS':
43 port = port or 689
47 port = port or 689
44 self.LDAP_USE_LDAPS = use_ldaps
48 ldap_server_type = ldap_server_type + 's'
49
45 self.TLS_REQCERT = ldap.__dict__['OPT_X_TLS_' + tls_reqcert]
50 self.TLS_REQCERT = ldap.__dict__['OPT_X_TLS_' + tls_reqcert]
46 self.LDAP_SERVER_ADDRESS = server
51 self.LDAP_SERVER_ADDRESS = server
47 self.LDAP_SERVER_PORT = port
52 self.LDAP_SERVER_PORT = port
48
53
49 #USE FOR READ ONLY BIND TO LDAP SERVER
54 #USE FOR READ ONLY BIND TO LDAP SERVER
50 self.LDAP_BIND_DN = bind_dn
55 self.LDAP_BIND_DN = bind_dn
51 self.LDAP_BIND_PASS = bind_pass
56 self.LDAP_BIND_PASS = bind_pass
52
57
53 ldap_server_type = 'ldap'
54 if self.LDAP_USE_LDAPS:ldap_server_type = ldap_server_type + 's'
55 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
58 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
56 self.LDAP_SERVER_ADDRESS,
59 self.LDAP_SERVER_ADDRESS,
57 self.LDAP_SERVER_PORT)
60 self.LDAP_SERVER_PORT)
58
61
59 self.BASE_DN = base_dn
62 self.BASE_DN = base_dn
60 self.LDAP_FILTER = ldap_filter
63 self.LDAP_FILTER = ldap_filter
61 self.SEARCH_SCOPE = ldap.__dict__['SCOPE_' + search_scope]
64 self.SEARCH_SCOPE = ldap.__dict__['SCOPE_' + search_scope]
62 self.attr_login = attr_login
65 self.attr_login = attr_login
63
66
64
67
65 def authenticate_ldap(self, username, password):
68 def authenticate_ldap(self, username, password):
66 """Authenticate a user via LDAP and return his/her LDAP properties.
69 """Authenticate a user via LDAP and return his/her LDAP properties.
67
70
68 Raises AuthenticationError if the credentials are rejected, or
71 Raises AuthenticationError if the credentials are rejected, or
69 EnvironmentError if the LDAP server can't be reached.
72 EnvironmentError if the LDAP server can't be reached.
70
73
71 :param username: username
74 :param username: username
72 :param password: password
75 :param password: password
73 """
76 """
74
77
75 from rhodecode.lib.helpers import chop_at
78 from rhodecode.lib.helpers import chop_at
76
79
77 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
80 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
78
81
79 if "," in username:
82 if "," in username:
80 raise LdapUsernameError("invalid character in username: ,")
83 raise LdapUsernameError("invalid character in username: ,")
81 try:
84 try:
82 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts')
85 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts')
83 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
86 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
84 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
87 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
85 ldap.set_option(ldap.OPT_TIMEOUT, 20)
88 ldap.set_option(ldap.OPT_TIMEOUT, 20)
86 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
89 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
87 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
90 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
88 if self.LDAP_USE_LDAPS:
91 if self.TLS_KIND != 'PLAIN':
89 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
92 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
90 server = ldap.initialize(self.LDAP_SERVER)
93 server = ldap.initialize(self.LDAP_SERVER)
91 if self.ldap_version == 2:
94 if self.ldap_version == 2:
92 server.protocol = ldap.VERSION2
95 server.protocol = ldap.VERSION2
93 else:
96 else:
94 server.protocol = ldap.VERSION3
97 server.protocol = ldap.VERSION3
95
98
99 if self.TLS_KIND == 'START_TLS':
100 server.start_tls_s()
101
96 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
102 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
97 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
103 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
98
104
99 filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login, username)
105 filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login, username)
100 log.debug("Authenticating %r filt %s at %s", self.BASE_DN,
106 log.debug("Authenticating %r filt %s at %s", self.BASE_DN,
101 filt, self.LDAP_SERVER)
107 filt, self.LDAP_SERVER)
102 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
108 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
103 filt)
109 filt)
104
110
105 if not lobjects:
111 if not lobjects:
106 raise ldap.NO_SUCH_OBJECT()
112 raise ldap.NO_SUCH_OBJECT()
107
113
108 for (dn, _attrs) in lobjects:
114 for (dn, _attrs) in lobjects:
109 try:
115 try:
110 server.simple_bind_s(dn, password)
116 server.simple_bind_s(dn, password)
111 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE, '(objectClass=*)')[0][1]
117 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE, '(objectClass=*)')[0][1]
112 break
118 break
113
119
114 except ldap.INVALID_CREDENTIALS, e:
120 except ldap.INVALID_CREDENTIALS, e:
115 log.debug("LDAP rejected password for user '%s' (%s): %s",
121 log.debug("LDAP rejected password for user '%s' (%s): %s",
116 uid, username, dn)
122 uid, username, dn)
117
123
118 else:
124 else:
119 log.debug("No matching LDAP objects for authentication "
125 log.debug("No matching LDAP objects for authentication "
120 "of '%s' (%s)", uid, username)
126 "of '%s' (%s)", uid, username)
121 raise LdapPasswordError()
127 raise LdapPasswordError()
122
128
123 except ldap.NO_SUCH_OBJECT, e:
129 except ldap.NO_SUCH_OBJECT, e:
124 log.debug("LDAP says no such user '%s' (%s)", uid, username)
130 log.debug("LDAP says no such user '%s' (%s)", uid, username)
125 raise LdapUsernameError()
131 raise LdapUsernameError()
126 except ldap.SERVER_DOWN, e:
132 except ldap.SERVER_DOWN, e:
127 raise LdapConnectionError("LDAP can't access authentication server")
133 raise LdapConnectionError("LDAP can't access authentication server")
128
134
129 return (dn, attrs)
135 return (dn, attrs)
@@ -1,514 +1,514
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.db_manage
3 rhodecode.lib.db_manage
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Database creation, and setup module for RhodeCode. Used for creation
6 Database creation, and setup module for RhodeCode. Used for creation
7 of database as well as for migration operations
7 of database as well as for migration operations
8
8
9 :created_on: Apr 10, 2010
9 :created_on: Apr 10, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import uuid
29 import uuid
30 import logging
30 import logging
31 from os.path import dirname as dn, join as jn
31 from os.path import dirname as dn, join as jn
32
32
33 from rhodecode import __dbversion__
33 from rhodecode import __dbversion__
34 from rhodecode.model import meta
34 from rhodecode.model import meta
35
35
36 from rhodecode.lib.auth import get_crypt_password, generate_api_key
36 from rhodecode.lib.auth import get_crypt_password, generate_api_key
37 from rhodecode.lib.utils import ask_ok
37 from rhodecode.lib.utils import ask_ok
38 from rhodecode.model import init_model
38 from rhodecode.model import init_model
39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
40 RhodeCodeSettings, UserToPerm, DbMigrateVersion
40 RhodeCodeSettings, UserToPerm, DbMigrateVersion
41
41
42 from sqlalchemy.engine import create_engine
42 from sqlalchemy.engine import create_engine
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class DbManage(object):
47 class DbManage(object):
48 def __init__(self, log_sql, dbconf, root, tests=False):
48 def __init__(self, log_sql, dbconf, root, tests=False):
49 self.dbname = dbconf.split('/')[-1]
49 self.dbname = dbconf.split('/')[-1]
50 self.tests = tests
50 self.tests = tests
51 self.root = root
51 self.root = root
52 self.dburi = dbconf
52 self.dburi = dbconf
53 self.log_sql = log_sql
53 self.log_sql = log_sql
54 self.db_exists = False
54 self.db_exists = False
55 self.init_db()
55 self.init_db()
56
56
57 def init_db(self):
57 def init_db(self):
58 engine = create_engine(self.dburi, echo=self.log_sql)
58 engine = create_engine(self.dburi, echo=self.log_sql)
59 init_model(engine)
59 init_model(engine)
60 self.sa = meta.Session()
60 self.sa = meta.Session()
61
61
62 def create_tables(self, override=False):
62 def create_tables(self, override=False):
63 """Create a auth database
63 """Create a auth database
64 """
64 """
65
65
66 log.info("Any existing database is going to be destroyed")
66 log.info("Any existing database is going to be destroyed")
67 if self.tests:
67 if self.tests:
68 destroy = True
68 destroy = True
69 else:
69 else:
70 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
70 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
71 if not destroy:
71 if not destroy:
72 sys.exit()
72 sys.exit()
73 if destroy:
73 if destroy:
74 meta.Base.metadata.drop_all()
74 meta.Base.metadata.drop_all()
75
75
76 checkfirst = not override
76 checkfirst = not override
77 meta.Base.metadata.create_all(checkfirst=checkfirst)
77 meta.Base.metadata.create_all(checkfirst=checkfirst)
78 log.info('Created tables for %s', self.dbname)
78 log.info('Created tables for %s', self.dbname)
79
79
80 def set_db_version(self):
80 def set_db_version(self):
81 try:
81 try:
82 ver = DbMigrateVersion()
82 ver = DbMigrateVersion()
83 ver.version = __dbversion__
83 ver.version = __dbversion__
84 ver.repository_id = 'rhodecode_db_migrations'
84 ver.repository_id = 'rhodecode_db_migrations'
85 ver.repository_path = 'versions'
85 ver.repository_path = 'versions'
86 self.sa.add(ver)
86 self.sa.add(ver)
87 self.sa.commit()
87 self.sa.commit()
88 except:
88 except:
89 self.sa.rollback()
89 self.sa.rollback()
90 raise
90 raise
91 log.info('db version set to: %s', __dbversion__)
91 log.info('db version set to: %s', __dbversion__)
92
92
93 def upgrade(self):
93 def upgrade(self):
94 """Upgrades given database schema to given revision following
94 """Upgrades given database schema to given revision following
95 all needed steps, to perform the upgrade
95 all needed steps, to perform the upgrade
96
96
97 """
97 """
98
98
99 from rhodecode.lib.dbmigrate.migrate.versioning import api
99 from rhodecode.lib.dbmigrate.migrate.versioning import api
100 from rhodecode.lib.dbmigrate.migrate.exceptions import \
100 from rhodecode.lib.dbmigrate.migrate.exceptions import \
101 DatabaseNotControlledError
101 DatabaseNotControlledError
102
102
103 upgrade = ask_ok('You are about to perform database upgrade, make '
103 upgrade = ask_ok('You are about to perform database upgrade, make '
104 'sure You backed up your database before. '
104 'sure You backed up your database before. '
105 'Continue ? [y/n]')
105 'Continue ? [y/n]')
106 if not upgrade:
106 if not upgrade:
107 sys.exit('Nothing done')
107 sys.exit('Nothing done')
108
108
109 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
109 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
110 'rhodecode/lib/dbmigrate')
110 'rhodecode/lib/dbmigrate')
111 db_uri = self.dburi
111 db_uri = self.dburi
112
112
113 try:
113 try:
114 curr_version = api.db_version(db_uri, repository_path)
114 curr_version = api.db_version(db_uri, repository_path)
115 msg = ('Found current database under version'
115 msg = ('Found current database under version'
116 ' control with version %s' % curr_version)
116 ' control with version %s' % curr_version)
117
117
118 except (RuntimeError, DatabaseNotControlledError), e:
118 except (RuntimeError, DatabaseNotControlledError), e:
119 curr_version = 1
119 curr_version = 1
120 msg = ('Current database is not under version control. Setting'
120 msg = ('Current database is not under version control. Setting'
121 ' as version %s' % curr_version)
121 ' as version %s' % curr_version)
122 api.version_control(db_uri, repository_path, curr_version)
122 api.version_control(db_uri, repository_path, curr_version)
123
123
124 print (msg)
124 print (msg)
125
125
126 if curr_version == __dbversion__:
126 if curr_version == __dbversion__:
127 sys.exit('This database is already at the newest version')
127 sys.exit('This database is already at the newest version')
128
128
129 #======================================================================
129 #======================================================================
130 # UPGRADE STEPS
130 # UPGRADE STEPS
131 #======================================================================
131 #======================================================================
132 class UpgradeSteps(object):
132 class UpgradeSteps(object):
133 """Those steps follow schema versions so for example schema
133 """Those steps follow schema versions so for example schema
134 for example schema with seq 002 == step_2 and so on.
134 for example schema with seq 002 == step_2 and so on.
135 """
135 """
136
136
137 def __init__(self, klass):
137 def __init__(self, klass):
138 self.klass = klass
138 self.klass = klass
139
139
140 def step_0(self):
140 def step_0(self):
141 #step 0 is the schema upgrade, and than follow proper upgrades
141 #step 0 is the schema upgrade, and than follow proper upgrades
142 print ('attempting to do database upgrade to version %s' \
142 print ('attempting to do database upgrade to version %s' \
143 % __dbversion__)
143 % __dbversion__)
144 api.upgrade(db_uri, repository_path, __dbversion__)
144 api.upgrade(db_uri, repository_path, __dbversion__)
145 print ('Schema upgrade completed')
145 print ('Schema upgrade completed')
146
146
147 def step_1(self):
147 def step_1(self):
148 pass
148 pass
149
149
150 def step_2(self):
150 def step_2(self):
151 print ('Patching repo paths for newer version of RhodeCode')
151 print ('Patching repo paths for newer version of RhodeCode')
152 self.klass.fix_repo_paths()
152 self.klass.fix_repo_paths()
153
153
154 print ('Patching default user of RhodeCode')
154 print ('Patching default user of RhodeCode')
155 self.klass.fix_default_user()
155 self.klass.fix_default_user()
156
156
157 log.info('Changing ui settings')
157 log.info('Changing ui settings')
158 self.klass.create_ui_settings()
158 self.klass.create_ui_settings()
159
159
160 def step_3(self):
160 def step_3(self):
161 print ('Adding additional settings into RhodeCode db')
161 print ('Adding additional settings into RhodeCode db')
162 self.klass.fix_settings()
162 self.klass.fix_settings()
163
163
164 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
164 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
165
165
166 #CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
166 #CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
167 for step in upgrade_steps:
167 for step in upgrade_steps:
168 print ('performing upgrade step %s' % step)
168 print ('performing upgrade step %s' % step)
169 callable = getattr(UpgradeSteps(self), 'step_%s' % step)()
169 callable = getattr(UpgradeSteps(self), 'step_%s' % step)()
170
170
171 def fix_repo_paths(self):
171 def fix_repo_paths(self):
172 """Fixes a old rhodecode version path into new one without a '*'
172 """Fixes a old rhodecode version path into new one without a '*'
173 """
173 """
174
174
175 paths = self.sa.query(RhodeCodeUi)\
175 paths = self.sa.query(RhodeCodeUi)\
176 .filter(RhodeCodeUi.ui_key == '/')\
176 .filter(RhodeCodeUi.ui_key == '/')\
177 .scalar()
177 .scalar()
178
178
179 paths.ui_value = paths.ui_value.replace('*', '')
179 paths.ui_value = paths.ui_value.replace('*', '')
180
180
181 try:
181 try:
182 self.sa.add(paths)
182 self.sa.add(paths)
183 self.sa.commit()
183 self.sa.commit()
184 except:
184 except:
185 self.sa.rollback()
185 self.sa.rollback()
186 raise
186 raise
187
187
188 def fix_default_user(self):
188 def fix_default_user(self):
189 """Fixes a old default user with some 'nicer' default values,
189 """Fixes a old default user with some 'nicer' default values,
190 used mostly for anonymous access
190 used mostly for anonymous access
191 """
191 """
192 def_user = self.sa.query(User)\
192 def_user = self.sa.query(User)\
193 .filter(User.username == 'default')\
193 .filter(User.username == 'default')\
194 .one()
194 .one()
195
195
196 def_user.name = 'Anonymous'
196 def_user.name = 'Anonymous'
197 def_user.lastname = 'User'
197 def_user.lastname = 'User'
198 def_user.email = 'anonymous@rhodecode.org'
198 def_user.email = 'anonymous@rhodecode.org'
199
199
200 try:
200 try:
201 self.sa.add(def_user)
201 self.sa.add(def_user)
202 self.sa.commit()
202 self.sa.commit()
203 except:
203 except:
204 self.sa.rollback()
204 self.sa.rollback()
205 raise
205 raise
206
206
207 def fix_settings(self):
207 def fix_settings(self):
208 """Fixes rhodecode settings adds ga_code key for google analytics
208 """Fixes rhodecode settings adds ga_code key for google analytics
209 """
209 """
210
210
211 hgsettings3 = RhodeCodeSettings('ga_code', '')
211 hgsettings3 = RhodeCodeSettings('ga_code', '')
212
212
213 try:
213 try:
214 self.sa.add(hgsettings3)
214 self.sa.add(hgsettings3)
215 self.sa.commit()
215 self.sa.commit()
216 except:
216 except:
217 self.sa.rollback()
217 self.sa.rollback()
218 raise
218 raise
219
219
220 def admin_prompt(self, second=False):
220 def admin_prompt(self, second=False):
221 if not self.tests:
221 if not self.tests:
222 import getpass
222 import getpass
223
223
224 def get_password():
224 def get_password():
225 password = getpass.getpass('Specify admin password '
225 password = getpass.getpass('Specify admin password '
226 '(min 6 chars):')
226 '(min 6 chars):')
227 confirm = getpass.getpass('Confirm password:')
227 confirm = getpass.getpass('Confirm password:')
228
228
229 if password != confirm:
229 if password != confirm:
230 log.error('passwords mismatch')
230 log.error('passwords mismatch')
231 return False
231 return False
232 if len(password) < 6:
232 if len(password) < 6:
233 log.error('password is to short use at least 6 characters')
233 log.error('password is to short use at least 6 characters')
234 return False
234 return False
235
235
236 return password
236 return password
237
237
238 username = raw_input('Specify admin username:')
238 username = raw_input('Specify admin username:')
239
239
240 password = get_password()
240 password = get_password()
241 if not password:
241 if not password:
242 #second try
242 #second try
243 password = get_password()
243 password = get_password()
244 if not password:
244 if not password:
245 sys.exit()
245 sys.exit()
246
246
247 email = raw_input('Specify admin email:')
247 email = raw_input('Specify admin email:')
248 self.create_user(username, password, email, True)
248 self.create_user(username, password, email, True)
249 else:
249 else:
250 log.info('creating admin and regular test users')
250 log.info('creating admin and regular test users')
251 self.create_user('test_admin', 'test12',
251 self.create_user('test_admin', 'test12',
252 'test_admin@mail.com', True)
252 'test_admin@mail.com', True)
253 self.create_user('test_regular', 'test12',
253 self.create_user('test_regular', 'test12',
254 'test_regular@mail.com', False)
254 'test_regular@mail.com', False)
255 self.create_user('test_regular2', 'test12',
255 self.create_user('test_regular2', 'test12',
256 'test_regular2@mail.com', False)
256 'test_regular2@mail.com', False)
257
257
258 def create_ui_settings(self):
258 def create_ui_settings(self):
259 """Creates ui settings, fills out hooks
259 """Creates ui settings, fills out hooks
260 and disables dotencode
260 and disables dotencode
261
261
262 """
262 """
263 #HOOKS
263 #HOOKS
264 hooks1_key = 'changegroup.update'
264 hooks1_key = 'changegroup.update'
265 hooks1_ = self.sa.query(RhodeCodeUi)\
265 hooks1_ = self.sa.query(RhodeCodeUi)\
266 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
266 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
267
267
268 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
268 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
269 hooks1.ui_section = 'hooks'
269 hooks1.ui_section = 'hooks'
270 hooks1.ui_key = hooks1_key
270 hooks1.ui_key = hooks1_key
271 hooks1.ui_value = 'hg update >&2'
271 hooks1.ui_value = 'hg update >&2'
272 hooks1.ui_active = False
272 hooks1.ui_active = False
273
273
274 hooks2_key = 'changegroup.repo_size'
274 hooks2_key = 'changegroup.repo_size'
275 hooks2_ = self.sa.query(RhodeCodeUi)\
275 hooks2_ = self.sa.query(RhodeCodeUi)\
276 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
276 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
277
277
278 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
278 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
279 hooks2.ui_section = 'hooks'
279 hooks2.ui_section = 'hooks'
280 hooks2.ui_key = hooks2_key
280 hooks2.ui_key = hooks2_key
281 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
281 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
282
282
283 hooks3 = RhodeCodeUi()
283 hooks3 = RhodeCodeUi()
284 hooks3.ui_section = 'hooks'
284 hooks3.ui_section = 'hooks'
285 hooks3.ui_key = 'pretxnchangegroup.push_logger'
285 hooks3.ui_key = 'pretxnchangegroup.push_logger'
286 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
286 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
287
287
288 hooks4 = RhodeCodeUi()
288 hooks4 = RhodeCodeUi()
289 hooks4.ui_section = 'hooks'
289 hooks4.ui_section = 'hooks'
290 hooks4.ui_key = 'preoutgoing.pull_logger'
290 hooks4.ui_key = 'preoutgoing.pull_logger'
291 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
291 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
292
292
293 #For mercurial 1.7 set backward comapatibility with format
293 #For mercurial 1.7 set backward comapatibility with format
294 dotencode_disable = RhodeCodeUi()
294 dotencode_disable = RhodeCodeUi()
295 dotencode_disable.ui_section = 'format'
295 dotencode_disable.ui_section = 'format'
296 dotencode_disable.ui_key = 'dotencode'
296 dotencode_disable.ui_key = 'dotencode'
297 dotencode_disable.ui_value = 'false'
297 dotencode_disable.ui_value = 'false'
298
298
299 try:
299 try:
300 self.sa.add(hooks1)
300 self.sa.add(hooks1)
301 self.sa.add(hooks2)
301 self.sa.add(hooks2)
302 self.sa.add(hooks3)
302 self.sa.add(hooks3)
303 self.sa.add(hooks4)
303 self.sa.add(hooks4)
304 self.sa.add(dotencode_disable)
304 self.sa.add(dotencode_disable)
305 self.sa.commit()
305 self.sa.commit()
306 except:
306 except:
307 self.sa.rollback()
307 self.sa.rollback()
308 raise
308 raise
309
309
310 def create_ldap_options(self):
310 def create_ldap_options(self):
311 """Creates ldap settings"""
311 """Creates ldap settings"""
312
312
313 try:
313 try:
314 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
314 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
315 ('ldap_port', '389'), ('ldap_ldaps', 'false'),
315 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
316 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
316 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
317 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
317 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
318 ('ldap_filter', ''), ('ldap_search_scope', ''),
318 ('ldap_filter', ''), ('ldap_search_scope', ''),
319 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
319 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
320 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
320 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
321
321
322 setting = RhodeCodeSettings(k, v)
322 setting = RhodeCodeSettings(k, v)
323 self.sa.add(setting)
323 self.sa.add(setting)
324 self.sa.commit()
324 self.sa.commit()
325 except:
325 except:
326 self.sa.rollback()
326 self.sa.rollback()
327 raise
327 raise
328
328
329 def config_prompt(self, test_repo_path='', retries=3):
329 def config_prompt(self, test_repo_path='', retries=3):
330 if retries == 3:
330 if retries == 3:
331 log.info('Setting up repositories config')
331 log.info('Setting up repositories config')
332
332
333 if not self.tests and not test_repo_path:
333 if not self.tests and not test_repo_path:
334 path = raw_input('Specify valid full path to your repositories'
334 path = raw_input('Specify valid full path to your repositories'
335 ' you can change this later in application settings:')
335 ' you can change this later in application settings:')
336 else:
336 else:
337 path = test_repo_path
337 path = test_repo_path
338 path_ok = True
338 path_ok = True
339
339
340 #check proper dir
340 #check proper dir
341 if not os.path.isdir(path):
341 if not os.path.isdir(path):
342 path_ok = False
342 path_ok = False
343 log.error('Entered path is not a valid directory: %s [%s/3]',
343 log.error('Entered path is not a valid directory: %s [%s/3]',
344 path, retries)
344 path, retries)
345
345
346 #check write access
346 #check write access
347 if not os.access(path, os.W_OK):
347 if not os.access(path, os.W_OK):
348 path_ok = False
348 path_ok = False
349
349
350 log.error('No write permission to given path: %s [%s/3]',
350 log.error('No write permission to given path: %s [%s/3]',
351 path, retries)
351 path, retries)
352
352
353 if retries == 0:
353 if retries == 0:
354 sys.exit()
354 sys.exit()
355 if path_ok is False:
355 if path_ok is False:
356 retries -= 1
356 retries -= 1
357 return self.config_prompt(test_repo_path, retries)
357 return self.config_prompt(test_repo_path, retries)
358
358
359 return path
359 return path
360
360
361 def create_settings(self, path):
361 def create_settings(self, path):
362
362
363 self.create_ui_settings()
363 self.create_ui_settings()
364
364
365 #HG UI OPTIONS
365 #HG UI OPTIONS
366 web1 = RhodeCodeUi()
366 web1 = RhodeCodeUi()
367 web1.ui_section = 'web'
367 web1.ui_section = 'web'
368 web1.ui_key = 'push_ssl'
368 web1.ui_key = 'push_ssl'
369 web1.ui_value = 'false'
369 web1.ui_value = 'false'
370
370
371 web2 = RhodeCodeUi()
371 web2 = RhodeCodeUi()
372 web2.ui_section = 'web'
372 web2.ui_section = 'web'
373 web2.ui_key = 'allow_archive'
373 web2.ui_key = 'allow_archive'
374 web2.ui_value = 'gz zip bz2'
374 web2.ui_value = 'gz zip bz2'
375
375
376 web3 = RhodeCodeUi()
376 web3 = RhodeCodeUi()
377 web3.ui_section = 'web'
377 web3.ui_section = 'web'
378 web3.ui_key = 'allow_push'
378 web3.ui_key = 'allow_push'
379 web3.ui_value = '*'
379 web3.ui_value = '*'
380
380
381 web4 = RhodeCodeUi()
381 web4 = RhodeCodeUi()
382 web4.ui_section = 'web'
382 web4.ui_section = 'web'
383 web4.ui_key = 'baseurl'
383 web4.ui_key = 'baseurl'
384 web4.ui_value = '/'
384 web4.ui_value = '/'
385
385
386 paths = RhodeCodeUi()
386 paths = RhodeCodeUi()
387 paths.ui_section = 'paths'
387 paths.ui_section = 'paths'
388 paths.ui_key = '/'
388 paths.ui_key = '/'
389 paths.ui_value = path
389 paths.ui_value = path
390
390
391 hgsettings1 = RhodeCodeSettings('realm', 'RhodeCode authentication')
391 hgsettings1 = RhodeCodeSettings('realm', 'RhodeCode authentication')
392 hgsettings2 = RhodeCodeSettings('title', 'RhodeCode')
392 hgsettings2 = RhodeCodeSettings('title', 'RhodeCode')
393 hgsettings3 = RhodeCodeSettings('ga_code', '')
393 hgsettings3 = RhodeCodeSettings('ga_code', '')
394
394
395 try:
395 try:
396 self.sa.add(web1)
396 self.sa.add(web1)
397 self.sa.add(web2)
397 self.sa.add(web2)
398 self.sa.add(web3)
398 self.sa.add(web3)
399 self.sa.add(web4)
399 self.sa.add(web4)
400 self.sa.add(paths)
400 self.sa.add(paths)
401 self.sa.add(hgsettings1)
401 self.sa.add(hgsettings1)
402 self.sa.add(hgsettings2)
402 self.sa.add(hgsettings2)
403 self.sa.add(hgsettings3)
403 self.sa.add(hgsettings3)
404
404
405 self.sa.commit()
405 self.sa.commit()
406 except:
406 except:
407 self.sa.rollback()
407 self.sa.rollback()
408 raise
408 raise
409
409
410 self.create_ldap_options()
410 self.create_ldap_options()
411
411
412 log.info('created ui config')
412 log.info('created ui config')
413
413
414 def create_user(self, username, password, email='', admin=False):
414 def create_user(self, username, password, email='', admin=False):
415 log.info('creating administrator user %s', username)
415 log.info('creating administrator user %s', username)
416 new_user = User()
416 new_user = User()
417 new_user.username = username
417 new_user.username = username
418 new_user.password = get_crypt_password(password)
418 new_user.password = get_crypt_password(password)
419 new_user.api_key = generate_api_key(username)
419 new_user.api_key = generate_api_key(username)
420 new_user.name = 'RhodeCode'
420 new_user.name = 'RhodeCode'
421 new_user.lastname = 'Admin'
421 new_user.lastname = 'Admin'
422 new_user.email = email
422 new_user.email = email
423 new_user.admin = admin
423 new_user.admin = admin
424 new_user.active = True
424 new_user.active = True
425
425
426 try:
426 try:
427 self.sa.add(new_user)
427 self.sa.add(new_user)
428 self.sa.commit()
428 self.sa.commit()
429 except:
429 except:
430 self.sa.rollback()
430 self.sa.rollback()
431 raise
431 raise
432
432
433 def create_default_user(self):
433 def create_default_user(self):
434 log.info('creating default user')
434 log.info('creating default user')
435 #create default user for handling default permissions.
435 #create default user for handling default permissions.
436 def_user = User()
436 def_user = User()
437 def_user.username = 'default'
437 def_user.username = 'default'
438 def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
438 def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
439 def_user.api_key = generate_api_key('default')
439 def_user.api_key = generate_api_key('default')
440 def_user.name = 'Anonymous'
440 def_user.name = 'Anonymous'
441 def_user.lastname = 'User'
441 def_user.lastname = 'User'
442 def_user.email = 'anonymous@rhodecode.org'
442 def_user.email = 'anonymous@rhodecode.org'
443 def_user.admin = False
443 def_user.admin = False
444 def_user.active = False
444 def_user.active = False
445 try:
445 try:
446 self.sa.add(def_user)
446 self.sa.add(def_user)
447 self.sa.commit()
447 self.sa.commit()
448 except:
448 except:
449 self.sa.rollback()
449 self.sa.rollback()
450 raise
450 raise
451
451
452 def create_permissions(self):
452 def create_permissions(self):
453 #module.(access|create|change|delete)_[name]
453 #module.(access|create|change|delete)_[name]
454 #module.(read|write|owner)
454 #module.(read|write|owner)
455 perms = [('repository.none', 'Repository no access'),
455 perms = [('repository.none', 'Repository no access'),
456 ('repository.read', 'Repository read access'),
456 ('repository.read', 'Repository read access'),
457 ('repository.write', 'Repository write access'),
457 ('repository.write', 'Repository write access'),
458 ('repository.admin', 'Repository admin access'),
458 ('repository.admin', 'Repository admin access'),
459 ('hg.admin', 'Hg Administrator'),
459 ('hg.admin', 'Hg Administrator'),
460 ('hg.create.repository', 'Repository create'),
460 ('hg.create.repository', 'Repository create'),
461 ('hg.create.none', 'Repository creation disabled'),
461 ('hg.create.none', 'Repository creation disabled'),
462 ('hg.register.none', 'Register disabled'),
462 ('hg.register.none', 'Register disabled'),
463 ('hg.register.manual_activate', 'Register new user with '
463 ('hg.register.manual_activate', 'Register new user with '
464 'RhodeCode without manual'
464 'RhodeCode without manual'
465 'activation'),
465 'activation'),
466
466
467 ('hg.register.auto_activate', 'Register new user with '
467 ('hg.register.auto_activate', 'Register new user with '
468 'RhodeCode without auto '
468 'RhodeCode without auto '
469 'activation'),
469 'activation'),
470 ]
470 ]
471
471
472 for p in perms:
472 for p in perms:
473 new_perm = Permission()
473 new_perm = Permission()
474 new_perm.permission_name = p[0]
474 new_perm.permission_name = p[0]
475 new_perm.permission_longname = p[1]
475 new_perm.permission_longname = p[1]
476 try:
476 try:
477 self.sa.add(new_perm)
477 self.sa.add(new_perm)
478 self.sa.commit()
478 self.sa.commit()
479 except:
479 except:
480 self.sa.rollback()
480 self.sa.rollback()
481 raise
481 raise
482
482
483 def populate_default_permissions(self):
483 def populate_default_permissions(self):
484 log.info('creating default user permissions')
484 log.info('creating default user permissions')
485
485
486 default_user = self.sa.query(User)\
486 default_user = self.sa.query(User)\
487 .filter(User.username == 'default').scalar()
487 .filter(User.username == 'default').scalar()
488
488
489 reg_perm = UserToPerm()
489 reg_perm = UserToPerm()
490 reg_perm.user = default_user
490 reg_perm.user = default_user
491 reg_perm.permission = self.sa.query(Permission)\
491 reg_perm.permission = self.sa.query(Permission)\
492 .filter(Permission.permission_name == 'hg.register.manual_activate')\
492 .filter(Permission.permission_name == 'hg.register.manual_activate')\
493 .scalar()
493 .scalar()
494
494
495 create_repo_perm = UserToPerm()
495 create_repo_perm = UserToPerm()
496 create_repo_perm.user = default_user
496 create_repo_perm.user = default_user
497 create_repo_perm.permission = self.sa.query(Permission)\
497 create_repo_perm.permission = self.sa.query(Permission)\
498 .filter(Permission.permission_name == 'hg.create.repository')\
498 .filter(Permission.permission_name == 'hg.create.repository')\
499 .scalar()
499 .scalar()
500
500
501 default_repo_perm = UserToPerm()
501 default_repo_perm = UserToPerm()
502 default_repo_perm.user = default_user
502 default_repo_perm.user = default_user
503 default_repo_perm.permission = self.sa.query(Permission)\
503 default_repo_perm.permission = self.sa.query(Permission)\
504 .filter(Permission.permission_name == 'repository.read')\
504 .filter(Permission.permission_name == 'repository.read')\
505 .scalar()
505 .scalar()
506
506
507 try:
507 try:
508 self.sa.add(reg_perm)
508 self.sa.add(reg_perm)
509 self.sa.add(create_repo_perm)
509 self.sa.add(create_repo_perm)
510 self.sa.add(default_repo_perm)
510 self.sa.add(default_repo_perm)
511 self.sa.commit()
511 self.sa.commit()
512 except:
512 except:
513 self.sa.rollback()
513 self.sa.rollback()
514 raise
514 raise
@@ -1,579 +1,579
1 """ this is forms validation classes
1 """ this is forms validation classes
2 http://formencode.org/module-formencode.validators.html
2 http://formencode.org/module-formencode.validators.html
3 for list off all availible validators
3 for list off all availible validators
4
4
5 we can create our own validators
5 we can create our own validators
6
6
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 pre_validators [] These validators will be applied before the schema
8 pre_validators [] These validators will be applied before the schema
9 chained_validators [] These validators will be applied after the schema
9 chained_validators [] These validators will be applied after the schema
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14
14
15
15
16 <name> = formencode.validators.<name of validator>
16 <name> = formencode.validators.<name of validator>
17 <name> must equal form name
17 <name> must equal form name
18 list=[1,2,3,4,5]
18 list=[1,2,3,4,5]
19 for SELECT use formencode.All(OneOf(list), Int())
19 for SELECT use formencode.All(OneOf(list), Int())
20
20
21 """
21 """
22 import os
22 import os
23 import re
23 import re
24 import logging
24 import logging
25
25
26 import formencode
26 import formencode
27 from formencode import All
27 from formencode import All
28 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
28 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
29 Email, Bool, StringBoolean, Set
29 Email, Bool, StringBoolean, Set
30
30
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32 from webhelpers.pylonslib.secure_form import authentication_token
32 from webhelpers.pylonslib.secure_form import authentication_token
33
33
34 from rhodecode.lib.utils import repo_name_slug
34 from rhodecode.lib.utils import repo_name_slug
35 from rhodecode.lib.auth import authenticate, get_crypt_password
35 from rhodecode.lib.auth import authenticate, get_crypt_password
36 from rhodecode.lib.exceptions import LdapImportError
36 from rhodecode.lib.exceptions import LdapImportError
37 from rhodecode.model import meta
37 from rhodecode.model import meta
38 from rhodecode.model.user import UserModel
38 from rhodecode.model.user import UserModel
39 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.db import User, UsersGroup
40 from rhodecode.model.db import User, UsersGroup
41 from rhodecode import BACKENDS
41 from rhodecode import BACKENDS
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45 #this is needed to translate the messages using _() in validators
45 #this is needed to translate the messages using _() in validators
46 class State_obj(object):
46 class State_obj(object):
47 _ = staticmethod(_)
47 _ = staticmethod(_)
48
48
49 #==============================================================================
49 #==============================================================================
50 # VALIDATORS
50 # VALIDATORS
51 #==============================================================================
51 #==============================================================================
52 class ValidAuthToken(formencode.validators.FancyValidator):
52 class ValidAuthToken(formencode.validators.FancyValidator):
53 messages = {'invalid_token':_('Token mismatch')}
53 messages = {'invalid_token':_('Token mismatch')}
54
54
55 def validate_python(self, value, state):
55 def validate_python(self, value, state):
56
56
57 if value != authentication_token():
57 if value != authentication_token():
58 raise formencode.Invalid(self.message('invalid_token', state,
58 raise formencode.Invalid(self.message('invalid_token', state,
59 search_number=value), value, state)
59 search_number=value), value, state)
60
60
61 def ValidUsername(edit, old_data):
61 def ValidUsername(edit, old_data):
62 class _ValidUsername(formencode.validators.FancyValidator):
62 class _ValidUsername(formencode.validators.FancyValidator):
63
63
64 def validate_python(self, value, state):
64 def validate_python(self, value, state):
65 if value in ['default', 'new_user']:
65 if value in ['default', 'new_user']:
66 raise formencode.Invalid(_('Invalid username'), value, state)
66 raise formencode.Invalid(_('Invalid username'), value, state)
67 #check if user is unique
67 #check if user is unique
68 old_un = None
68 old_un = None
69 if edit:
69 if edit:
70 old_un = UserModel().get(old_data.get('user_id')).username
70 old_un = UserModel().get(old_data.get('user_id')).username
71
71
72 if old_un != value or not edit:
72 if old_un != value or not edit:
73 if UserModel().get_by_username(value, cache=False,
73 if UserModel().get_by_username(value, cache=False,
74 case_insensitive=True):
74 case_insensitive=True):
75 raise formencode.Invalid(_('This username already '
75 raise formencode.Invalid(_('This username already '
76 'exists') , value, state)
76 'exists') , value, state)
77
77
78 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
78 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
79 raise formencode.Invalid(_('Username may only contain '
79 raise formencode.Invalid(_('Username may only contain '
80 'alphanumeric characters '
80 'alphanumeric characters '
81 'underscores, periods or dashes '
81 'underscores, periods or dashes '
82 'and must begin with alphanumeric '
82 'and must begin with alphanumeric '
83 'character'), value, state)
83 'character'), value, state)
84
84
85 return _ValidUsername
85 return _ValidUsername
86
86
87
87
88 def ValidUsersGroup(edit, old_data):
88 def ValidUsersGroup(edit, old_data):
89
89
90 class _ValidUsersGroup(formencode.validators.FancyValidator):
90 class _ValidUsersGroup(formencode.validators.FancyValidator):
91
91
92 def validate_python(self, value, state):
92 def validate_python(self, value, state):
93 if value in ['default']:
93 if value in ['default']:
94 raise formencode.Invalid(_('Invalid group name'), value, state)
94 raise formencode.Invalid(_('Invalid group name'), value, state)
95 #check if group is unique
95 #check if group is unique
96 old_ugname = None
96 old_ugname = None
97 if edit:
97 if edit:
98 old_ugname = UsersGroup.get(
98 old_ugname = UsersGroup.get(
99 old_data.get('users_group_id')).users_group_name
99 old_data.get('users_group_id')).users_group_name
100
100
101 if old_ugname != value or not edit:
101 if old_ugname != value or not edit:
102 if UsersGroup.get_by_group_name(value, cache=False,
102 if UsersGroup.get_by_group_name(value, cache=False,
103 case_insensitive=True):
103 case_insensitive=True):
104 raise formencode.Invalid(_('This users group '
104 raise formencode.Invalid(_('This users group '
105 'already exists') , value,
105 'already exists') , value,
106 state)
106 state)
107
107
108
108
109 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
109 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
110 raise formencode.Invalid(_('Group name may only contain '
110 raise formencode.Invalid(_('Group name may only contain '
111 'alphanumeric characters '
111 'alphanumeric characters '
112 'underscores, periods or dashes '
112 'underscores, periods or dashes '
113 'and must begin with alphanumeric '
113 'and must begin with alphanumeric '
114 'character'), value, state)
114 'character'), value, state)
115
115
116 return _ValidUsersGroup
116 return _ValidUsersGroup
117
117
118
118
119
119
120 class ValidPassword(formencode.validators.FancyValidator):
120 class ValidPassword(formencode.validators.FancyValidator):
121
121
122 def to_python(self, value, state):
122 def to_python(self, value, state):
123
123
124 if value:
124 if value:
125
125
126 if value.get('password'):
126 if value.get('password'):
127 try:
127 try:
128 value['password'] = get_crypt_password(value['password'])
128 value['password'] = get_crypt_password(value['password'])
129 except UnicodeEncodeError:
129 except UnicodeEncodeError:
130 e_dict = {'password':_('Invalid characters in password')}
130 e_dict = {'password':_('Invalid characters in password')}
131 raise formencode.Invalid('', value, state, error_dict=e_dict)
131 raise formencode.Invalid('', value, state, error_dict=e_dict)
132
132
133 if value.get('password_confirmation'):
133 if value.get('password_confirmation'):
134 try:
134 try:
135 value['password_confirmation'] = \
135 value['password_confirmation'] = \
136 get_crypt_password(value['password_confirmation'])
136 get_crypt_password(value['password_confirmation'])
137 except UnicodeEncodeError:
137 except UnicodeEncodeError:
138 e_dict = {'password_confirmation':_('Invalid characters in password')}
138 e_dict = {'password_confirmation':_('Invalid characters in password')}
139 raise formencode.Invalid('', value, state, error_dict=e_dict)
139 raise formencode.Invalid('', value, state, error_dict=e_dict)
140
140
141 if value.get('new_password'):
141 if value.get('new_password'):
142 try:
142 try:
143 value['new_password'] = \
143 value['new_password'] = \
144 get_crypt_password(value['new_password'])
144 get_crypt_password(value['new_password'])
145 except UnicodeEncodeError:
145 except UnicodeEncodeError:
146 e_dict = {'new_password':_('Invalid characters in password')}
146 e_dict = {'new_password':_('Invalid characters in password')}
147 raise formencode.Invalid('', value, state, error_dict=e_dict)
147 raise formencode.Invalid('', value, state, error_dict=e_dict)
148
148
149 return value
149 return value
150
150
151 class ValidPasswordsMatch(formencode.validators.FancyValidator):
151 class ValidPasswordsMatch(formencode.validators.FancyValidator):
152
152
153 def validate_python(self, value, state):
153 def validate_python(self, value, state):
154
154
155 if value['password'] != value['password_confirmation']:
155 if value['password'] != value['password_confirmation']:
156 e_dict = {'password_confirmation':
156 e_dict = {'password_confirmation':
157 _('Password do not match')}
157 _('Password do not match')}
158 raise formencode.Invalid('', value, state, error_dict=e_dict)
158 raise formencode.Invalid('', value, state, error_dict=e_dict)
159
159
160 class ValidAuth(formencode.validators.FancyValidator):
160 class ValidAuth(formencode.validators.FancyValidator):
161 messages = {
161 messages = {
162 'invalid_password':_('invalid password'),
162 'invalid_password':_('invalid password'),
163 'invalid_login':_('invalid user name'),
163 'invalid_login':_('invalid user name'),
164 'disabled_account':_('Your account is disabled')
164 'disabled_account':_('Your account is disabled')
165
165
166 }
166 }
167 #error mapping
167 #error mapping
168 e_dict = {'username':messages['invalid_login'],
168 e_dict = {'username':messages['invalid_login'],
169 'password':messages['invalid_password']}
169 'password':messages['invalid_password']}
170 e_dict_disable = {'username':messages['disabled_account']}
170 e_dict_disable = {'username':messages['disabled_account']}
171
171
172 def validate_python(self, value, state):
172 def validate_python(self, value, state):
173 password = value['password']
173 password = value['password']
174 username = value['username']
174 username = value['username']
175 user = UserModel().get_by_username(username)
175 user = UserModel().get_by_username(username)
176
176
177 if authenticate(username, password):
177 if authenticate(username, password):
178 return value
178 return value
179 else:
179 else:
180 if user and user.active is False:
180 if user and user.active is False:
181 log.warning('user %s is disabled', username)
181 log.warning('user %s is disabled', username)
182 raise formencode.Invalid(self.message('disabled_account',
182 raise formencode.Invalid(self.message('disabled_account',
183 state=State_obj),
183 state=State_obj),
184 value, state,
184 value, state,
185 error_dict=self.e_dict_disable)
185 error_dict=self.e_dict_disable)
186 else:
186 else:
187 log.warning('user %s not authenticated', username)
187 log.warning('user %s not authenticated', username)
188 raise formencode.Invalid(self.message('invalid_password',
188 raise formencode.Invalid(self.message('invalid_password',
189 state=State_obj), value, state,
189 state=State_obj), value, state,
190 error_dict=self.e_dict)
190 error_dict=self.e_dict)
191
191
192 class ValidRepoUser(formencode.validators.FancyValidator):
192 class ValidRepoUser(formencode.validators.FancyValidator):
193
193
194 def to_python(self, value, state):
194 def to_python(self, value, state):
195 sa = meta.Session()
195 sa = meta.Session()
196 try:
196 try:
197 self.user_db = sa.query(User)\
197 self.user_db = sa.query(User)\
198 .filter(User.active == True)\
198 .filter(User.active == True)\
199 .filter(User.username == value).one()
199 .filter(User.username == value).one()
200 except Exception:
200 except Exception:
201 raise formencode.Invalid(_('This username is not valid'),
201 raise formencode.Invalid(_('This username is not valid'),
202 value, state)
202 value, state)
203 finally:
203 finally:
204 meta.Session.remove()
204 meta.Session.remove()
205
205
206 return self.user_db.user_id
206 return self.user_db.user_id
207
207
208 def ValidRepoName(edit, old_data):
208 def ValidRepoName(edit, old_data):
209 class _ValidRepoName(formencode.validators.FancyValidator):
209 class _ValidRepoName(formencode.validators.FancyValidator):
210
210
211 def to_python(self, value, state):
211 def to_python(self, value, state):
212 slug = repo_name_slug(value)
212 slug = repo_name_slug(value)
213 if slug in ['_admin']:
213 if slug in ['_admin']:
214 raise formencode.Invalid(_('This repository name is disallowed'),
214 raise formencode.Invalid(_('This repository name is disallowed'),
215 value, state)
215 value, state)
216 if old_data.get('repo_name') != value or not edit:
216 if old_data.get('repo_name') != value or not edit:
217 if RepoModel().get_by_repo_name(slug, cache=False):
217 if RepoModel().get_by_repo_name(slug, cache=False):
218 raise formencode.Invalid(_('This repository already exists') ,
218 raise formencode.Invalid(_('This repository already exists') ,
219 value, state)
219 value, state)
220 return slug
220 return slug
221
221
222
222
223 return _ValidRepoName
223 return _ValidRepoName
224
224
225 def ValidCloneUri():
225 def ValidCloneUri():
226 from mercurial.httprepo import httprepository, httpsrepository
226 from mercurial.httprepo import httprepository, httpsrepository
227 from rhodecode.lib.utils import make_ui
227 from rhodecode.lib.utils import make_ui
228
228
229 class _ValidCloneUri(formencode.validators.FancyValidator):
229 class _ValidCloneUri(formencode.validators.FancyValidator):
230 def to_python(self, value, state):
230 def to_python(self, value, state):
231 if not value:
231 if not value:
232 pass
232 pass
233 elif value.startswith('https'):
233 elif value.startswith('https'):
234 try:
234 try:
235 httpsrepository(make_ui('db'), value).capabilities()
235 httpsrepository(make_ui('db'), value).capabilities()
236 except:
236 except:
237 raise formencode.Invalid(_('invalid clone url'), value,
237 raise formencode.Invalid(_('invalid clone url'), value,
238 state)
238 state)
239 elif value.startswith('http'):
239 elif value.startswith('http'):
240 try:
240 try:
241 httprepository(make_ui('db'), value).capabilities()
241 httprepository(make_ui('db'), value).capabilities()
242 except:
242 except:
243 raise formencode.Invalid(_('invalid clone url'), value,
243 raise formencode.Invalid(_('invalid clone url'), value,
244 state)
244 state)
245 else:
245 else:
246 raise formencode.Invalid(_('Invalid clone url, provide a '
246 raise formencode.Invalid(_('Invalid clone url, provide a '
247 'valid clone http\s url'), value,
247 'valid clone http\s url'), value,
248 state)
248 state)
249
249
250 return _ValidCloneUri
250 return _ValidCloneUri
251
251
252 def ValidForkType(old_data):
252 def ValidForkType(old_data):
253 class _ValidForkType(formencode.validators.FancyValidator):
253 class _ValidForkType(formencode.validators.FancyValidator):
254
254
255 def to_python(self, value, state):
255 def to_python(self, value, state):
256 if old_data['repo_type'] != value:
256 if old_data['repo_type'] != value:
257 raise formencode.Invalid(_('Fork have to be the same '
257 raise formencode.Invalid(_('Fork have to be the same '
258 'type as original'), value, state)
258 'type as original'), value, state)
259 return value
259 return value
260 return _ValidForkType
260 return _ValidForkType
261
261
262 class ValidPerms(formencode.validators.FancyValidator):
262 class ValidPerms(formencode.validators.FancyValidator):
263 messages = {'perm_new_member_name':_('This username or users group name'
263 messages = {'perm_new_member_name':_('This username or users group name'
264 ' is not valid')}
264 ' is not valid')}
265
265
266 def to_python(self, value, state):
266 def to_python(self, value, state):
267 perms_update = []
267 perms_update = []
268 perms_new = []
268 perms_new = []
269 #build a list of permission to update and new permission to create
269 #build a list of permission to update and new permission to create
270 for k, v in value.items():
270 for k, v in value.items():
271 #means new added member to permissions
271 #means new added member to permissions
272 if k.startswith('perm_new_member'):
272 if k.startswith('perm_new_member'):
273 new_perm = value.get('perm_new_member', False)
273 new_perm = value.get('perm_new_member', False)
274 new_member = value.get('perm_new_member_name', False)
274 new_member = value.get('perm_new_member_name', False)
275 new_type = value.get('perm_new_member_type')
275 new_type = value.get('perm_new_member_type')
276
276
277 if new_member and new_perm:
277 if new_member and new_perm:
278 if (new_member, new_perm, new_type) not in perms_new:
278 if (new_member, new_perm, new_type) not in perms_new:
279 perms_new.append((new_member, new_perm, new_type))
279 perms_new.append((new_member, new_perm, new_type))
280 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
280 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
281 member = k[7:]
281 member = k[7:]
282 t = {'u':'user',
282 t = {'u':'user',
283 'g':'users_group'}[k[0]]
283 'g':'users_group'}[k[0]]
284 if member == 'default':
284 if member == 'default':
285 if value['private']:
285 if value['private']:
286 #set none for default when updating to private repo
286 #set none for default when updating to private repo
287 v = 'repository.none'
287 v = 'repository.none'
288 perms_update.append((member, v, t))
288 perms_update.append((member, v, t))
289
289
290 value['perms_updates'] = perms_update
290 value['perms_updates'] = perms_update
291 value['perms_new'] = perms_new
291 value['perms_new'] = perms_new
292
292
293 #update permissions
293 #update permissions
294 sa = meta.Session
294 sa = meta.Session
295 for k, v, t in perms_new:
295 for k, v, t in perms_new:
296 try:
296 try:
297 if t is 'user':
297 if t is 'user':
298 self.user_db = sa.query(User)\
298 self.user_db = sa.query(User)\
299 .filter(User.active == True)\
299 .filter(User.active == True)\
300 .filter(User.username == k).one()
300 .filter(User.username == k).one()
301 if t is 'users_group':
301 if t is 'users_group':
302 self.user_db = sa.query(UsersGroup)\
302 self.user_db = sa.query(UsersGroup)\
303 .filter(UsersGroup.users_group_active == True)\
303 .filter(UsersGroup.users_group_active == True)\
304 .filter(UsersGroup.users_group_name == k).one()
304 .filter(UsersGroup.users_group_name == k).one()
305
305
306 except Exception:
306 except Exception:
307 msg = self.message('perm_new_member_name',
307 msg = self.message('perm_new_member_name',
308 state=State_obj)
308 state=State_obj)
309 raise formencode.Invalid(msg, value, state,
309 raise formencode.Invalid(msg, value, state,
310 error_dict={'perm_new_member_name':msg})
310 error_dict={'perm_new_member_name':msg})
311 return value
311 return value
312
312
313 class ValidSettings(formencode.validators.FancyValidator):
313 class ValidSettings(formencode.validators.FancyValidator):
314
314
315 def to_python(self, value, state):
315 def to_python(self, value, state):
316 #settings form can't edit user
316 #settings form can't edit user
317 if value.has_key('user'):
317 if value.has_key('user'):
318 del['value']['user']
318 del['value']['user']
319
319
320 return value
320 return value
321
321
322 class ValidPath(formencode.validators.FancyValidator):
322 class ValidPath(formencode.validators.FancyValidator):
323 def to_python(self, value, state):
323 def to_python(self, value, state):
324
324
325 if not os.path.isdir(value):
325 if not os.path.isdir(value):
326 msg = _('This is not a valid path')
326 msg = _('This is not a valid path')
327 raise formencode.Invalid(msg, value, state,
327 raise formencode.Invalid(msg, value, state,
328 error_dict={'paths_root_path':msg})
328 error_dict={'paths_root_path':msg})
329 return value
329 return value
330
330
331 def UniqSystemEmail(old_data):
331 def UniqSystemEmail(old_data):
332 class _UniqSystemEmail(formencode.validators.FancyValidator):
332 class _UniqSystemEmail(formencode.validators.FancyValidator):
333 def to_python(self, value, state):
333 def to_python(self, value, state):
334 value = value.lower()
334 value = value.lower()
335 if old_data.get('email') != value:
335 if old_data.get('email') != value:
336 sa = meta.Session()
336 sa = meta.Session()
337 try:
337 try:
338 user = sa.query(User).filter(User.email == value).scalar()
338 user = sa.query(User).filter(User.email == value).scalar()
339 if user:
339 if user:
340 raise formencode.Invalid(_("This e-mail address is already taken") ,
340 raise formencode.Invalid(_("This e-mail address is already taken") ,
341 value, state)
341 value, state)
342 finally:
342 finally:
343 meta.Session.remove()
343 meta.Session.remove()
344
344
345 return value
345 return value
346
346
347 return _UniqSystemEmail
347 return _UniqSystemEmail
348
348
349 class ValidSystemEmail(formencode.validators.FancyValidator):
349 class ValidSystemEmail(formencode.validators.FancyValidator):
350 def to_python(self, value, state):
350 def to_python(self, value, state):
351 value = value.lower()
351 value = value.lower()
352 sa = meta.Session
352 sa = meta.Session
353 try:
353 try:
354 user = sa.query(User).filter(User.email == value).scalar()
354 user = sa.query(User).filter(User.email == value).scalar()
355 if user is None:
355 if user is None:
356 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
356 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
357 value, state)
357 value, state)
358 finally:
358 finally:
359 meta.Session.remove()
359 meta.Session.remove()
360
360
361 return value
361 return value
362
362
363 class LdapLibValidator(formencode.validators.FancyValidator):
363 class LdapLibValidator(formencode.validators.FancyValidator):
364
364
365 def to_python(self, value, state):
365 def to_python(self, value, state):
366
366
367 try:
367 try:
368 import ldap
368 import ldap
369 except ImportError:
369 except ImportError:
370 raise LdapImportError
370 raise LdapImportError
371 return value
371 return value
372
372
373 class AttrLoginValidator(formencode.validators.FancyValidator):
373 class AttrLoginValidator(formencode.validators.FancyValidator):
374
374
375 def to_python(self, value, state):
375 def to_python(self, value, state):
376
376
377 if not value or not isinstance(value, (str, unicode)):
377 if not value or not isinstance(value, (str, unicode)):
378 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
378 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
379 "must be specified - this is the name "
379 "must be specified - this is the name "
380 "of the attribute that is equivalent "
380 "of the attribute that is equivalent "
381 "to 'username'"),
381 "to 'username'"),
382 value, state)
382 value, state)
383
383
384 return value
384 return value
385
385
386 #===============================================================================
386 #===============================================================================
387 # FORMS
387 # FORMS
388 #===============================================================================
388 #===============================================================================
389 class LoginForm(formencode.Schema):
389 class LoginForm(formencode.Schema):
390 allow_extra_fields = True
390 allow_extra_fields = True
391 filter_extra_fields = True
391 filter_extra_fields = True
392 username = UnicodeString(
392 username = UnicodeString(
393 strip=True,
393 strip=True,
394 min=1,
394 min=1,
395 not_empty=True,
395 not_empty=True,
396 messages={
396 messages={
397 'empty':_('Please enter a login'),
397 'empty':_('Please enter a login'),
398 'tooShort':_('Enter a value %(min)i characters long or more')}
398 'tooShort':_('Enter a value %(min)i characters long or more')}
399 )
399 )
400
400
401 password = UnicodeString(
401 password = UnicodeString(
402 strip=True,
402 strip=True,
403 min=6,
403 min=6,
404 not_empty=True,
404 not_empty=True,
405 messages={
405 messages={
406 'empty':_('Please enter a password'),
406 'empty':_('Please enter a password'),
407 'tooShort':_('Enter %(min)i characters or more')}
407 'tooShort':_('Enter %(min)i characters or more')}
408 )
408 )
409
409
410
410
411 #chained validators have access to all data
411 #chained validators have access to all data
412 chained_validators = [ValidAuth]
412 chained_validators = [ValidAuth]
413
413
414 def UserForm(edit=False, old_data={}):
414 def UserForm(edit=False, old_data={}):
415 class _UserForm(formencode.Schema):
415 class _UserForm(formencode.Schema):
416 allow_extra_fields = True
416 allow_extra_fields = True
417 filter_extra_fields = True
417 filter_extra_fields = True
418 username = All(UnicodeString(strip=True, min=1, not_empty=True),
418 username = All(UnicodeString(strip=True, min=1, not_empty=True),
419 ValidUsername(edit, old_data))
419 ValidUsername(edit, old_data))
420 if edit:
420 if edit:
421 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
421 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
422 admin = StringBoolean(if_missing=False)
422 admin = StringBoolean(if_missing=False)
423 else:
423 else:
424 password = All(UnicodeString(strip=True, min=6, not_empty=True))
424 password = All(UnicodeString(strip=True, min=6, not_empty=True))
425 active = StringBoolean(if_missing=False)
425 active = StringBoolean(if_missing=False)
426 name = UnicodeString(strip=True, min=1, not_empty=True)
426 name = UnicodeString(strip=True, min=1, not_empty=True)
427 lastname = UnicodeString(strip=True, min=1, not_empty=True)
427 lastname = UnicodeString(strip=True, min=1, not_empty=True)
428 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
428 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
429
429
430 chained_validators = [ValidPassword]
430 chained_validators = [ValidPassword]
431
431
432 return _UserForm
432 return _UserForm
433
433
434
434
435 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
435 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
436 class _UsersGroupForm(formencode.Schema):
436 class _UsersGroupForm(formencode.Schema):
437 allow_extra_fields = True
437 allow_extra_fields = True
438 filter_extra_fields = True
438 filter_extra_fields = True
439
439
440 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
440 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
441 ValidUsersGroup(edit, old_data))
441 ValidUsersGroup(edit, old_data))
442
442
443 users_group_active = StringBoolean(if_missing=False)
443 users_group_active = StringBoolean(if_missing=False)
444
444
445 if edit:
445 if edit:
446 users_group_members = OneOf(available_members, hideList=False,
446 users_group_members = OneOf(available_members, hideList=False,
447 testValueList=True,
447 testValueList=True,
448 if_missing=None, not_empty=False)
448 if_missing=None, not_empty=False)
449
449
450 return _UsersGroupForm
450 return _UsersGroupForm
451
451
452 def RegisterForm(edit=False, old_data={}):
452 def RegisterForm(edit=False, old_data={}):
453 class _RegisterForm(formencode.Schema):
453 class _RegisterForm(formencode.Schema):
454 allow_extra_fields = True
454 allow_extra_fields = True
455 filter_extra_fields = True
455 filter_extra_fields = True
456 username = All(ValidUsername(edit, old_data),
456 username = All(ValidUsername(edit, old_data),
457 UnicodeString(strip=True, min=1, not_empty=True))
457 UnicodeString(strip=True, min=1, not_empty=True))
458 password = All(UnicodeString(strip=True, min=6, not_empty=True))
458 password = All(UnicodeString(strip=True, min=6, not_empty=True))
459 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
459 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
460 active = StringBoolean(if_missing=False)
460 active = StringBoolean(if_missing=False)
461 name = UnicodeString(strip=True, min=1, not_empty=True)
461 name = UnicodeString(strip=True, min=1, not_empty=True)
462 lastname = UnicodeString(strip=True, min=1, not_empty=True)
462 lastname = UnicodeString(strip=True, min=1, not_empty=True)
463 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
463 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
464
464
465 chained_validators = [ValidPasswordsMatch, ValidPassword]
465 chained_validators = [ValidPasswordsMatch, ValidPassword]
466
466
467 return _RegisterForm
467 return _RegisterForm
468
468
469 def PasswordResetForm():
469 def PasswordResetForm():
470 class _PasswordResetForm(formencode.Schema):
470 class _PasswordResetForm(formencode.Schema):
471 allow_extra_fields = True
471 allow_extra_fields = True
472 filter_extra_fields = True
472 filter_extra_fields = True
473 email = All(ValidSystemEmail(), Email(not_empty=True))
473 email = All(ValidSystemEmail(), Email(not_empty=True))
474 return _PasswordResetForm
474 return _PasswordResetForm
475
475
476 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
476 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
477 repo_groups=[]):
477 repo_groups=[]):
478 class _RepoForm(formencode.Schema):
478 class _RepoForm(formencode.Schema):
479 allow_extra_fields = True
479 allow_extra_fields = True
480 filter_extra_fields = False
480 filter_extra_fields = False
481 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
481 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
482 ValidRepoName(edit, old_data))
482 ValidRepoName(edit, old_data))
483 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
483 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
484 ValidCloneUri()())
484 ValidCloneUri()())
485 repo_group = OneOf(repo_groups, hideList=True)
485 repo_group = OneOf(repo_groups, hideList=True)
486 repo_type = OneOf(supported_backends)
486 repo_type = OneOf(supported_backends)
487 description = UnicodeString(strip=True, min=1, not_empty=True)
487 description = UnicodeString(strip=True, min=1, not_empty=True)
488 private = StringBoolean(if_missing=False)
488 private = StringBoolean(if_missing=False)
489 enable_statistics = StringBoolean(if_missing=False)
489 enable_statistics = StringBoolean(if_missing=False)
490 enable_downloads = StringBoolean(if_missing=False)
490 enable_downloads = StringBoolean(if_missing=False)
491
491
492 if edit:
492 if edit:
493 #this is repo owner
493 #this is repo owner
494 user = All(Int(not_empty=True), ValidRepoUser)
494 user = All(Int(not_empty=True), ValidRepoUser)
495
495
496 chained_validators = [ValidPerms]
496 chained_validators = [ValidPerms]
497 return _RepoForm
497 return _RepoForm
498
498
499 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
499 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
500 class _RepoForkForm(formencode.Schema):
500 class _RepoForkForm(formencode.Schema):
501 allow_extra_fields = True
501 allow_extra_fields = True
502 filter_extra_fields = False
502 filter_extra_fields = False
503 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
503 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
504 ValidRepoName(edit, old_data))
504 ValidRepoName(edit, old_data))
505 description = UnicodeString(strip=True, min=1, not_empty=True)
505 description = UnicodeString(strip=True, min=1, not_empty=True)
506 private = StringBoolean(if_missing=False)
506 private = StringBoolean(if_missing=False)
507 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
507 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
508 return _RepoForkForm
508 return _RepoForkForm
509
509
510 def RepoSettingsForm(edit=False, old_data={}):
510 def RepoSettingsForm(edit=False, old_data={}):
511 class _RepoForm(formencode.Schema):
511 class _RepoForm(formencode.Schema):
512 allow_extra_fields = True
512 allow_extra_fields = True
513 filter_extra_fields = False
513 filter_extra_fields = False
514 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
514 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
515 ValidRepoName(edit, old_data))
515 ValidRepoName(edit, old_data))
516 description = UnicodeString(strip=True, min=1, not_empty=True)
516 description = UnicodeString(strip=True, min=1, not_empty=True)
517 private = StringBoolean(if_missing=False)
517 private = StringBoolean(if_missing=False)
518
518
519 chained_validators = [ValidPerms, ValidSettings]
519 chained_validators = [ValidPerms, ValidSettings]
520 return _RepoForm
520 return _RepoForm
521
521
522
522
523 def ApplicationSettingsForm():
523 def ApplicationSettingsForm():
524 class _ApplicationSettingsForm(formencode.Schema):
524 class _ApplicationSettingsForm(formencode.Schema):
525 allow_extra_fields = True
525 allow_extra_fields = True
526 filter_extra_fields = False
526 filter_extra_fields = False
527 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
527 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
528 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
528 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
529 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
529 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
530
530
531 return _ApplicationSettingsForm
531 return _ApplicationSettingsForm
532
532
533 def ApplicationUiSettingsForm():
533 def ApplicationUiSettingsForm():
534 class _ApplicationUiSettingsForm(formencode.Schema):
534 class _ApplicationUiSettingsForm(formencode.Schema):
535 allow_extra_fields = True
535 allow_extra_fields = True
536 filter_extra_fields = False
536 filter_extra_fields = False
537 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
537 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
538 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
538 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
539 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
539 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
540 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
540 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
541 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
541 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
542 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
542 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
543
543
544 return _ApplicationUiSettingsForm
544 return _ApplicationUiSettingsForm
545
545
546 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
546 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
547 class _DefaultPermissionsForm(formencode.Schema):
547 class _DefaultPermissionsForm(formencode.Schema):
548 allow_extra_fields = True
548 allow_extra_fields = True
549 filter_extra_fields = True
549 filter_extra_fields = True
550 overwrite_default = StringBoolean(if_missing=False)
550 overwrite_default = StringBoolean(if_missing=False)
551 anonymous = OneOf(['True', 'False'], if_missing=False)
551 anonymous = OneOf(['True', 'False'], if_missing=False)
552 default_perm = OneOf(perms_choices)
552 default_perm = OneOf(perms_choices)
553 default_register = OneOf(register_choices)
553 default_register = OneOf(register_choices)
554 default_create = OneOf(create_choices)
554 default_create = OneOf(create_choices)
555
555
556 return _DefaultPermissionsForm
556 return _DefaultPermissionsForm
557
557
558
558
559 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices):
559 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
560 class _LdapSettingsForm(formencode.Schema):
560 class _LdapSettingsForm(formencode.Schema):
561 allow_extra_fields = True
561 allow_extra_fields = True
562 filter_extra_fields = True
562 filter_extra_fields = True
563 pre_validators = [LdapLibValidator]
563 pre_validators = [LdapLibValidator]
564 ldap_active = StringBoolean(if_missing=False)
564 ldap_active = StringBoolean(if_missing=False)
565 ldap_host = UnicodeString(strip=True,)
565 ldap_host = UnicodeString(strip=True,)
566 ldap_port = Number(strip=True,)
566 ldap_port = Number(strip=True,)
567 ldap_ldaps = StringBoolean(if_missing=False)
567 ldap_tls_kind = OneOf(tls_kind_choices)
568 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
568 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
569 ldap_dn_user = UnicodeString(strip=True,)
569 ldap_dn_user = UnicodeString(strip=True,)
570 ldap_dn_pass = UnicodeString(strip=True,)
570 ldap_dn_pass = UnicodeString(strip=True,)
571 ldap_base_dn = UnicodeString(strip=True,)
571 ldap_base_dn = UnicodeString(strip=True,)
572 ldap_filter = UnicodeString(strip=True,)
572 ldap_filter = UnicodeString(strip=True,)
573 ldap_search_scope = OneOf(search_scope_choices)
573 ldap_search_scope = OneOf(search_scope_choices)
574 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
574 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
575 ldap_attr_firstname = UnicodeString(strip=True,)
575 ldap_attr_firstname = UnicodeString(strip=True,)
576 ldap_attr_lastname = UnicodeString(strip=True,)
576 ldap_attr_lastname = UnicodeString(strip=True,)
577 ldap_attr_email = UnicodeString(strip=True,)
577 ldap_attr_email = UnicodeString(strip=True,)
578
578
579 return _LdapSettingsForm
579 return _LdapSettingsForm
@@ -1,103 +1,103
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.settings
3 rhodecode.model.settings
4 ~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Settings model for RhodeCode
6 Settings model for RhodeCode
7
7
8 :created on Nov 17, 2010
8 :created on Nov 17, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27
27
28 from rhodecode.model import BaseModel
28 from rhodecode.model import BaseModel
29 from rhodecode.model.caching_query import FromCache
29 from rhodecode.model.caching_query import FromCache
30 from rhodecode.model.db import RhodeCodeSettings
30 from rhodecode.model.db import RhodeCodeSettings
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34 class SettingsModel(BaseModel):
34 class SettingsModel(BaseModel):
35 """
35 """
36 Settings model
36 Settings model
37 """
37 """
38
38
39 def get(self, settings_key, cache=False):
39 def get(self, settings_key, cache=False):
40 r = self.sa.query(RhodeCodeSettings)\
40 r = self.sa.query(RhodeCodeSettings)\
41 .filter(RhodeCodeSettings.app_settings_name == settings_key).scalar()
41 .filter(RhodeCodeSettings.app_settings_name == settings_key).scalar()
42 if cache:
42 if cache:
43 r = r.options(FromCache("sql_cache_short",
43 r = r.options(FromCache("sql_cache_short",
44 "get_setting_%s" % settings_key))
44 "get_setting_%s" % settings_key))
45 return r
45 return r
46
46
47 def get_app_settings(self, cache=False):
47 def get_app_settings(self, cache=False):
48 """Get's config from database, each config key is prefixed with
48 """Get's config from database, each config key is prefixed with
49 'rhodecode_' prefix, than global pylons config is updated with such
49 'rhodecode_' prefix, than global pylons config is updated with such
50 keys
50 keys
51 """
51 """
52
52
53 ret = self.sa.query(RhodeCodeSettings)
53 ret = self.sa.query(RhodeCodeSettings)
54
54
55 if cache:
55 if cache:
56 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
56 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
57
57
58 if not ret:
58 if not ret:
59 raise Exception('Could not get application settings !')
59 raise Exception('Could not get application settings !')
60 settings = {}
60 settings = {}
61 for each in ret:
61 for each in ret:
62 settings['rhodecode_' + each.app_settings_name] = each.app_settings_value
62 settings['rhodecode_' + each.app_settings_name] = each.app_settings_value
63
63
64 return settings
64 return settings
65
65
66 def get_ldap_settings(self):
66 def get_ldap_settings(self):
67 """
67 """
68 Returns ldap settings from database
68 Returns ldap settings from database
69 :returns:
69 :returns:
70 ldap_active
70 ldap_active
71 ldap_host
71 ldap_host
72 ldap_port
72 ldap_port
73 ldap_ldaps
73 ldap_tls_kind
74 ldap_tls_reqcert
74 ldap_tls_reqcert
75 ldap_dn_user
75 ldap_dn_user
76 ldap_dn_pass
76 ldap_dn_pass
77 ldap_base_dn
77 ldap_base_dn
78 ldap_filter
78 ldap_filter
79 ldap_search_scope
79 ldap_search_scope
80 ldap_attr_login
80 ldap_attr_login
81 ldap_attr_firstname
81 ldap_attr_firstname
82 ldap_attr_lastname
82 ldap_attr_lastname
83 ldap_attr_email
83 ldap_attr_email
84 """
84 """
85 # ldap_search_scope
85 # ldap_search_scope
86
86
87 r = self.sa.query(RhodeCodeSettings)\
87 r = self.sa.query(RhodeCodeSettings)\
88 .filter(RhodeCodeSettings.app_settings_name\
88 .filter(RhodeCodeSettings.app_settings_name\
89 .startswith('ldap_'))\
89 .startswith('ldap_'))\
90 .all()
90 .all()
91
91
92 fd = {}
92 fd = {}
93
93
94 for row in r:
94 for row in r:
95 v = row.app_settings_value
95 v = row.app_settings_value
96 if v in ['true', 'yes', 'on', 'y', 't', '1']:
96 if v in ['true', 'yes', 'on', 'y', 't', '1']:
97 v = True
97 v = True
98 elif v in ['false', 'no', 'off', 'n', 'f', '0']:
98 elif v in ['false', 'no', 'off', 'n', 'f', '0']:
99 v = False
99 v = False
100
100
101 fd.update({row.app_settings_name:v})
101 fd.update({row.app_settings_name:v})
102
102
103 return fd
103 return fd
@@ -1,103 +1,103
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('LDAP administration')} - ${c.rhodecode_name}
5 ${_('LDAP administration')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
10 &raquo;
11 ${_('Ldap')}
11 ${_('Ldap')}
12 </%def>
12 </%def>
13
13
14 <%def name="page_nav()">
14 <%def name="page_nav()">
15 ${self.menu('admin')}
15 ${self.menu('admin')}
16 </%def>
16 </%def>
17
17
18 <%def name="main()">
18 <%def name="main()">
19 <div class="box">
19 <div class="box">
20 <!-- box / title -->
20 <!-- box / title -->
21 <div class="title">
21 <div class="title">
22 ${self.breadcrumbs()}
22 ${self.breadcrumbs()}
23 </div>
23 </div>
24 ${h.form(url('ldap_settings'))}
24 ${h.form(url('ldap_settings'))}
25 <div class="form">
25 <div class="form">
26 <div class="fields">
26 <div class="fields">
27
27
28 <h3>${_('Connection settings')}</h3>
28 <h3>${_('Connection settings')}</h3>
29 <div class="field">
29 <div class="field">
30 <div class="label label-checkbox"><label for="ldap_active">${_('Enable LDAP')}</label></div>
30 <div class="label label-checkbox"><label for="ldap_active">${_('Enable LDAP')}</label></div>
31 <div class="checkboxes"><div class="checkbox">${h.checkbox('ldap_active',True,class_='small')}</div></div>
31 <div class="checkboxes"><div class="checkbox">${h.checkbox('ldap_active',True,class_='small')}</div></div>
32 </div>
32 </div>
33 <div class="field">
33 <div class="field">
34 <div class="label"><label for="ldap_host">${_('Host')}</label></div>
34 <div class="label"><label for="ldap_host">${_('Host')}</label></div>
35 <div class="input">${h.text('ldap_host',class_='small')}</div>
35 <div class="input">${h.text('ldap_host',class_='small')}</div>
36 </div>
36 </div>
37 <div class="field">
37 <div class="field">
38 <div class="label"><label for="ldap_port">${_('Port')}</label></div>
38 <div class="label"><label for="ldap_port">${_('Port')}</label></div>
39 <div class="input">${h.text('ldap_port',class_='small')}</div>
39 <div class="input">${h.text('ldap_port',class_='small')}</div>
40 </div>
40 </div>
41 <div class="field">
41 <div class="field">
42 <div class="label"><label for="ldap_dn_user">${_('Account')}</label></div>
42 <div class="label"><label for="ldap_dn_user">${_('Account')}</label></div>
43 <div class="input">${h.text('ldap_dn_user',class_='small')}</div>
43 <div class="input">${h.text('ldap_dn_user',class_='small')}</div>
44 </div>
44 </div>
45 <div class="field">
45 <div class="field">
46 <div class="label"><label for="ldap_dn_pass">${_('Password')}</label></div>
46 <div class="label"><label for="ldap_dn_pass">${_('Password')}</label></div>
47 <div class="input">${h.password('ldap_dn_pass',class_='small')}</div>
47 <div class="input">${h.password('ldap_dn_pass',class_='small')}</div>
48 </div>
48 </div>
49 <div class="field">
49 <div class="field">
50 <div class="label label-checkbox"><label for="ldap_ldaps">${_('Enable LDAPS')}</label></div>
50 <div class="label"><label for="ldap_tls_kind">${_('Connection security')}</label></div>
51 <div class="checkboxes"><div class="checkbox">${h.checkbox('ldap_ldaps',True,class_='small')}</div></div>
51 <div class="select">${h.select('ldap_tls_kind',c.tls_kind_cur,c.tls_kind_choices,class_='small')}</div>
52 </div>
52 </div>
53 <div class="field">
53 <div class="field">
54 <div class="label"><label for="ldap_tls_reqcert">${_('Certificate Checks')}</label></div>
54 <div class="label"><label for="ldap_tls_reqcert">${_('Certificate Checks')}</label></div>
55 <div class="select">${h.select('ldap_tls_reqcert',c.tls_reqcert_cur,c.tls_reqcert_choices,class_='small')}</div>
55 <div class="select">${h.select('ldap_tls_reqcert',c.tls_reqcert_cur,c.tls_reqcert_choices,class_='small')}</div>
56 </div>
56 </div>
57 <h3>${_('Search settings')}</h3>
57 <h3>${_('Search settings')}</h3>
58 <div class="field">
58 <div class="field">
59 <div class="label"><label for="ldap_base_dn">${_('Base DN')}</label></div>
59 <div class="label"><label for="ldap_base_dn">${_('Base DN')}</label></div>
60 <div class="input">${h.text('ldap_base_dn',class_='small')}</div>
60 <div class="input">${h.text('ldap_base_dn',class_='small')}</div>
61 </div>
61 </div>
62 <div class="field">
62 <div class="field">
63 <div class="label"><label for="ldap_filter">${_('LDAP Filter')}</label></div>
63 <div class="label"><label for="ldap_filter">${_('LDAP Filter')}</label></div>
64 <div class="input">${h.text('ldap_filter',class_='small')}</div>
64 <div class="input">${h.text('ldap_filter',class_='small')}</div>
65 </div>
65 </div>
66 <div class="field">
66 <div class="field">
67 <div class="label"><label for="ldap_search_scope">${_('LDAP Search Scope')}</label></div>
67 <div class="label"><label for="ldap_search_scope">${_('LDAP Search Scope')}</label></div>
68 <div class="select">${h.select('ldap_search_scope',c.search_scope_cur,c.search_scope_choices,class_='small')}</div>
68 <div class="select">${h.select('ldap_search_scope',c.search_scope_cur,c.search_scope_choices,class_='small')}</div>
69 </div>
69 </div>
70 <h3>${_('Attribute mappings')}</h3>
70 <h3>${_('Attribute mappings')}</h3>
71 <div class="field">
71 <div class="field">
72 <div class="label"><label for="ldap_attr_login">${_('Login Attribute')}</label></div>
72 <div class="label"><label for="ldap_attr_login">${_('Login Attribute')}</label></div>
73 <div class="input">${h.text('ldap_attr_login',class_='small')}</div>
73 <div class="input">${h.text('ldap_attr_login',class_='small')}</div>
74 </div>
74 </div>
75 <div class="field">
75 <div class="field">
76 <div class="label"><label for="ldap_attr_firstname">${_('First Name Attribute')}</label></div>
76 <div class="label"><label for="ldap_attr_firstname">${_('First Name Attribute')}</label></div>
77 <div class="input">${h.text('ldap_attr_firstname',class_='small')}</div>
77 <div class="input">${h.text('ldap_attr_firstname',class_='small')}</div>
78 </div>
78 </div>
79 <div class="field">
79 <div class="field">
80 <div class="label"><label for="ldap_attr_lastname">${_('Last Name Attribute')}</label></div>
80 <div class="label"><label for="ldap_attr_lastname">${_('Last Name Attribute')}</label></div>
81 <div class="input">${h.text('ldap_attr_lastname',class_='small')}</div>
81 <div class="input">${h.text('ldap_attr_lastname',class_='small')}</div>
82 </div>
82 </div>
83 <div class="field">
83 <div class="field">
84 <div class="label"><label for="ldap_attr_email">${_('E-mail Attribute')}</label></div>
84 <div class="label"><label for="ldap_attr_email">${_('E-mail Attribute')}</label></div>
85 <div class="input">${h.text('ldap_attr_email',class_='small')}</div>
85 <div class="input">${h.text('ldap_attr_email',class_='small')}</div>
86 </div>
86 </div>
87
87
88 <div class="buttons">
88 <div class="buttons">
89 ${h.submit('save','Save',class_="ui-button")}
89 ${h.submit('save','Save',class_="ui-button")}
90 </div>
90 </div>
91 </div>
91 </div>
92 </div>
92 </div>
93 ${h.end_form()}
93 ${h.end_form()}
94 </div>
94 </div>
95 </%def>
95 </%def>
96
96
97
97
98
98
99
99
100
100
101
101
102
102
103
103
General Comments 0
You need to be logged in to leave comments. Login now