##// END OF EJS Templates
authn: Add whitespace stripping to authentication plugin settings.
johbo -
r55:0ca726bb default
parent child Browse files
Show More
@@ -1,220 +1,223 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import colander
22 22 import logging
23 23
24 24 from sqlalchemy.ext.hybrid import hybrid_property
25 25
26 26 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
27 27 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
28 28 from rhodecode.authentication.routes import AuthnPluginResourceBase
29 from rhodecode.lib.colander_utils import strip_whitespace
29 30 from rhodecode.lib.utils2 import str2bool, safe_unicode
30 31 from rhodecode.model.db import User
31 32 from rhodecode.translation import _
32 33
33 34
34 35 log = logging.getLogger(__name__)
35 36
36 37
37 38 def plugin_factory(plugin_id, *args, **kwds):
38 39 """
39 40 Factory function that is called during plugin discovery.
40 41 It returns the plugin instance.
41 42 """
42 43 plugin = RhodeCodeAuthPlugin(plugin_id)
43 44 return plugin
44 45
45 46
46 47 class ContainerAuthnResource(AuthnPluginResourceBase):
47 48 pass
48 49
49 50
50 51 class ContainerSettingsSchema(AuthnPluginSettingsSchemaBase):
51 52 header = colander.SchemaNode(
52 53 colander.String(),
53 54 default='REMOTE_USER',
54 55 description=_('Header to extract the user from'),
56 preparer=strip_whitespace,
55 57 title=_('Header'),
56 58 widget='string')
57 59 fallback_header = colander.SchemaNode(
58 60 colander.String(),
59 61 default='HTTP_X_FORWARDED_USER',
60 62 description=_('Header to extract the user from when main one fails'),
63 preparer=strip_whitespace,
61 64 title=_('Fallback header'),
62 65 widget='string')
63 66 clean_username = colander.SchemaNode(
64 67 colander.Boolean(),
65 68 default=True,
66 69 description=_('Perform cleaning of user, if passed user has @ in '
67 70 'username then first part before @ is taken. '
68 71 'If there\'s \\ in the username only the part after '
69 72 ' \\ is taken'),
70 73 missing=False,
71 74 title=_('Clean username'),
72 75 widget='bool')
73 76
74 77
75 78 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
76 79
77 80 def includeme(self, config):
78 81 config.add_authn_plugin(self)
79 82 config.add_authn_resource(self.get_id(), ContainerAuthnResource(self))
80 83 config.add_view(
81 84 'rhodecode.authentication.views.AuthnPluginViewBase',
82 85 attr='settings_get',
83 86 request_method='GET',
84 87 route_name='auth_home',
85 88 context=ContainerAuthnResource)
86 89 config.add_view(
87 90 'rhodecode.authentication.views.AuthnPluginViewBase',
88 91 attr='settings_post',
89 92 request_method='POST',
90 93 route_name='auth_home',
91 94 context=ContainerAuthnResource)
92 95
93 96 def get_display_name(self):
94 97 return _('Container')
95 98
96 99 def get_settings_schema(self):
97 100 return ContainerSettingsSchema()
98 101
99 102 @hybrid_property
100 103 def name(self):
101 104 return "container"
102 105
103 106 @hybrid_property
104 107 def is_container_auth(self):
105 108 return True
106 109
107 110 def use_fake_password(self):
108 111 return True
109 112
110 113 def user_activation_state(self):
111 114 def_user_perms = User.get_default_user().AuthUser.permissions['global']
112 115 return 'hg.extern_activate.auto' in def_user_perms
113 116
114 117 def _clean_username(self, username):
115 118 # Removing realm and domain from username
116 119 username = username.split('@')[0]
117 120 username = username.rsplit('\\')[-1]
118 121 return username
119 122
120 123 def _get_username(self, environ, settings):
121 124 username = None
122 125 environ = environ or {}
123 126 if not environ:
124 127 log.debug('got empty environ: %s' % environ)
125 128
126 129 settings = settings or {}
127 130 if settings.get('header'):
128 131 header = settings.get('header')
129 132 username = environ.get(header)
130 133 log.debug('extracted %s:%s' % (header, username))
131 134
132 135 # fallback mode
133 136 if not username and settings.get('fallback_header'):
134 137 header = settings.get('fallback_header')
135 138 username = environ.get(header)
136 139 log.debug('extracted %s:%s' % (header, username))
137 140
138 141 if username and str2bool(settings.get('clean_username')):
139 142 log.debug('Received username `%s` from container' % username)
140 143 username = self._clean_username(username)
141 144 log.debug('New cleanup user is:%s' % username)
142 145 return username
143 146
144 147 def get_user(self, username=None, **kwargs):
145 148 """
146 149 Helper method for user fetching in plugins, by default it's using
147 150 simple fetch by username, but this method can be custimized in plugins
148 151 eg. container auth plugin to fetch user by environ params
149 152 :param username: username if given to fetch
150 153 :param kwargs: extra arguments needed for user fetching.
151 154 """
152 155 environ = kwargs.get('environ') or {}
153 156 settings = kwargs.get('settings') or {}
154 157 username = self._get_username(environ, settings)
155 158 # we got the username, so use default method now
156 159 return super(RhodeCodeAuthPlugin, self).get_user(username)
157 160
158 161 def auth(self, userobj, username, password, settings, **kwargs):
159 162 """
160 163 Get's the container_auth username (or email). It tries to get username
161 164 from REMOTE_USER if this plugin is enabled, if that fails
162 165 it tries to get username from HTTP_X_FORWARDED_USER if fallback header
163 166 is set. clean_username extracts the username from this data if it's
164 167 having @ in it.
165 168 Return None on failure. On success, return a dictionary of the form:
166 169
167 170 see: RhodeCodeAuthPluginBase.auth_func_attrs
168 171
169 172 :param userobj:
170 173 :param username:
171 174 :param password:
172 175 :param settings:
173 176 :param kwargs:
174 177 """
175 178 environ = kwargs.get('environ')
176 179 if not environ:
177 180 log.debug('Empty environ data skipping...')
178 181 return None
179 182
180 183 if not userobj:
181 184 userobj = self.get_user('', environ=environ, settings=settings)
182 185
183 186 # we don't care passed username/password for container auth plugins.
184 187 # only way to log in is using environ
185 188 username = None
186 189 if userobj:
187 190 username = getattr(userobj, 'username')
188 191
189 192 if not username:
190 193 # we don't have any objects in DB user doesn't exist extrac username
191 194 # from environ based on the settings
192 195 username = self._get_username(environ, settings)
193 196
194 197 # if cannot fetch username, it's a no-go for this plugin to proceed
195 198 if not username:
196 199 return None
197 200
198 201 # old attrs fetched from RhodeCode database
199 202 admin = getattr(userobj, 'admin', False)
200 203 active = getattr(userobj, 'active', True)
201 204 email = getattr(userobj, 'email', '')
202 205 firstname = getattr(userobj, 'firstname', '')
203 206 lastname = getattr(userobj, 'lastname', '')
204 207 extern_type = getattr(userobj, 'extern_type', '')
205 208
206 209 user_attrs = {
207 210 'username': username,
208 211 'firstname': safe_unicode(firstname or username),
209 212 'lastname': safe_unicode(lastname or ''),
210 213 'groups': [],
211 214 'email': email or '',
212 215 'admin': admin or False,
213 216 'active': active,
214 217 'active_from_extern': True,
215 218 'extern_name': username,
216 219 'extern_type': extern_type,
217 220 }
218 221
219 222 log.info('user `%s` authenticated correctly' % user_attrs['username'])
220 223 return user_attrs
@@ -1,276 +1,282 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 RhodeCode authentication plugin for Atlassian CROWD
23 23 """
24 24
25 25
26 26 import colander
27 27 import base64
28 28 import logging
29 29 import urllib2
30 30
31 31 from pylons.i18n.translation import lazy_ugettext as _
32 32 from sqlalchemy.ext.hybrid import hybrid_property
33 33
34 34 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
35 35 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
36 36 from rhodecode.authentication.routes import AuthnPluginResourceBase
37 from rhodecode.lib.colander_utils import strip_whitespace
37 38 from rhodecode.lib.ext_json import json, formatted_json
38 39 from rhodecode.model.db import User
39 40
40 41 log = logging.getLogger(__name__)
41 42
42 43
43 44 def plugin_factory(plugin_id, *args, **kwds):
44 45 """
45 46 Factory function that is called during plugin discovery.
46 47 It returns the plugin instance.
47 48 """
48 49 plugin = RhodeCodeAuthPlugin(plugin_id)
49 50 return plugin
50 51
51 52
52 53 class CrowdAuthnResource(AuthnPluginResourceBase):
53 54 pass
54 55
55 56
56 57 class CrowdSettingsSchema(AuthnPluginSettingsSchemaBase):
57 58 host = colander.SchemaNode(
58 59 colander.String(),
59 60 default='127.0.0.1',
60 61 description=_('The FQDN or IP of the Atlassian CROWD Server'),
62 preparer=strip_whitespace,
61 63 title=_('Host'),
62 64 widget='string')
63 65 port = colander.SchemaNode(
64 66 colander.Int(),
65 67 default=8095,
66 68 description=_('The Port in use by the Atlassian CROWD Server'),
69 preparer=strip_whitespace,
67 70 title=_('Port'),
68 71 validator=colander.Range(min=0, max=65536),
69 72 widget='int')
70 73 app_name = colander.SchemaNode(
71 74 colander.String(),
72 75 default='',
73 76 description=_('The Application Name to authenticate to CROWD'),
77 preparer=strip_whitespace,
74 78 title=_('Application Name'),
75 79 widget='string')
76 80 app_password = colander.SchemaNode(
77 81 colander.String(),
78 82 default='',
79 83 description=_('The password to authenticate to CROWD'),
84 preparer=strip_whitespace,
80 85 title=_('Application Password'),
81 86 widget='password')
82 87 admin_groups = colander.SchemaNode(
83 88 colander.String(),
84 89 default='',
85 90 description=_('A comma separated list of group names that identify '
86 91 'users as RhodeCode Administrators'),
87 92 missing='',
93 preparer=strip_whitespace,
88 94 title=_('Admin Groups'),
89 95 widget='string')
90 96
91 97
92 98 class CrowdServer(object):
93 99 def __init__(self, *args, **kwargs):
94 100 """
95 101 Create a new CrowdServer object that points to IP/Address 'host',
96 102 on the given port, and using the given method (https/http). user and
97 103 passwd can be set here or with set_credentials. If unspecified,
98 104 "version" defaults to "latest".
99 105
100 106 example::
101 107
102 108 cserver = CrowdServer(host="127.0.0.1",
103 109 port="8095",
104 110 user="some_app",
105 111 passwd="some_passwd",
106 112 version="1")
107 113 """
108 114 if not "port" in kwargs:
109 115 kwargs["port"] = "8095"
110 116 self._logger = kwargs.get("logger", logging.getLogger(__name__))
111 117 self._uri = "%s://%s:%s/crowd" % (kwargs.get("method", "http"),
112 118 kwargs.get("host", "127.0.0.1"),
113 119 kwargs.get("port", "8095"))
114 120 self.set_credentials(kwargs.get("user", ""),
115 121 kwargs.get("passwd", ""))
116 122 self._version = kwargs.get("version", "latest")
117 123 self._url_list = None
118 124 self._appname = "crowd"
119 125
120 126 def set_credentials(self, user, passwd):
121 127 self.user = user
122 128 self.passwd = passwd
123 129 self._make_opener()
124 130
125 131 def _make_opener(self):
126 132 mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
127 133 mgr.add_password(None, self._uri, self.user, self.passwd)
128 134 handler = urllib2.HTTPBasicAuthHandler(mgr)
129 135 self.opener = urllib2.build_opener(handler)
130 136
131 137 def _request(self, url, body=None, headers=None,
132 138 method=None, noformat=False,
133 139 empty_response_ok=False):
134 140 _headers = {"Content-type": "application/json",
135 141 "Accept": "application/json"}
136 142 if self.user and self.passwd:
137 143 authstring = base64.b64encode("%s:%s" % (self.user, self.passwd))
138 144 _headers["Authorization"] = "Basic %s" % authstring
139 145 if headers:
140 146 _headers.update(headers)
141 147 log.debug("Sent crowd: \n%s"
142 148 % (formatted_json({"url": url, "body": body,
143 149 "headers": _headers})))
144 150 request = urllib2.Request(url, body, _headers)
145 151 if method:
146 152 request.get_method = lambda: method
147 153
148 154 global msg
149 155 msg = ""
150 156 try:
151 157 rdoc = self.opener.open(request)
152 158 msg = "".join(rdoc.readlines())
153 159 if not msg and empty_response_ok:
154 160 rval = {}
155 161 rval["status"] = True
156 162 rval["error"] = "Response body was empty"
157 163 elif not noformat:
158 164 rval = json.loads(msg)
159 165 rval["status"] = True
160 166 else:
161 167 rval = "".join(rdoc.readlines())
162 168 except Exception as e:
163 169 if not noformat:
164 170 rval = {"status": False,
165 171 "body": body,
166 172 "error": str(e) + "\n" + msg}
167 173 else:
168 174 rval = None
169 175 return rval
170 176
171 177 def user_auth(self, username, password):
172 178 """Authenticate a user against crowd. Returns brief information about
173 179 the user."""
174 180 url = ("%s/rest/usermanagement/%s/authentication?username=%s"
175 181 % (self._uri, self._version, username))
176 182 body = json.dumps({"value": password})
177 183 return self._request(url, body)
178 184
179 185 def user_groups(self, username):
180 186 """Retrieve a list of groups to which this user belongs."""
181 187 url = ("%s/rest/usermanagement/%s/user/group/nested?username=%s"
182 188 % (self._uri, self._version, username))
183 189 return self._request(url)
184 190
185 191
186 192 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
187 193
188 194 def includeme(self, config):
189 195 config.add_authn_plugin(self)
190 196 config.add_authn_resource(self.get_id(), CrowdAuthnResource(self))
191 197 config.add_view(
192 198 'rhodecode.authentication.views.AuthnPluginViewBase',
193 199 attr='settings_get',
194 200 request_method='GET',
195 201 route_name='auth_home',
196 202 context=CrowdAuthnResource)
197 203 config.add_view(
198 204 'rhodecode.authentication.views.AuthnPluginViewBase',
199 205 attr='settings_post',
200 206 request_method='POST',
201 207 route_name='auth_home',
202 208 context=CrowdAuthnResource)
203 209
204 210 def get_settings_schema(self):
205 211 return CrowdSettingsSchema()
206 212
207 213 def get_display_name(self):
208 214 return _('CROWD')
209 215
210 216 @hybrid_property
211 217 def name(self):
212 218 return "crowd"
213 219
214 220 def use_fake_password(self):
215 221 return True
216 222
217 223 def user_activation_state(self):
218 224 def_user_perms = User.get_default_user().AuthUser.permissions['global']
219 225 return 'hg.extern_activate.auto' in def_user_perms
220 226
221 227 def auth(self, userobj, username, password, settings, **kwargs):
222 228 """
223 229 Given a user object (which may be null), username, a plaintext password,
224 230 and a settings object (containing all the keys needed as listed in settings()),
225 231 authenticate this user's login attempt.
226 232
227 233 Return None on failure. On success, return a dictionary of the form:
228 234
229 235 see: RhodeCodeAuthPluginBase.auth_func_attrs
230 236 This is later validated for correctness
231 237 """
232 238 if not username or not password:
233 239 log.debug('Empty username or password skipping...')
234 240 return None
235 241
236 242 log.debug("Crowd settings: \n%s" % (formatted_json(settings)))
237 243 server = CrowdServer(**settings)
238 244 server.set_credentials(settings["app_name"], settings["app_password"])
239 245 crowd_user = server.user_auth(username, password)
240 246 log.debug("Crowd returned: \n%s" % (formatted_json(crowd_user)))
241 247 if not crowd_user["status"]:
242 248 return None
243 249
244 250 res = server.user_groups(crowd_user["name"])
245 251 log.debug("Crowd groups: \n%s" % (formatted_json(res)))
246 252 crowd_user["groups"] = [x["name"] for x in res["groups"]]
247 253
248 254 # old attrs fetched from RhodeCode database
249 255 admin = getattr(userobj, 'admin', False)
250 256 active = getattr(userobj, 'active', True)
251 257 email = getattr(userobj, 'email', '')
252 258 username = getattr(userobj, 'username', username)
253 259 firstname = getattr(userobj, 'firstname', '')
254 260 lastname = getattr(userobj, 'lastname', '')
255 261 extern_type = getattr(userobj, 'extern_type', '')
256 262
257 263 user_attrs = {
258 264 'username': username,
259 265 'firstname': crowd_user["first-name"] or firstname,
260 266 'lastname': crowd_user["last-name"] or lastname,
261 267 'groups': crowd_user["groups"],
262 268 'email': crowd_user["email"] or email,
263 269 'admin': admin,
264 270 'active': active,
265 271 'active_from_extern': crowd_user.get('active'),
266 272 'extern_name': crowd_user["name"],
267 273 'extern_type': extern_type,
268 274 }
269 275
270 276 # set an admin if we're in admin_groups of crowd
271 277 for group in settings["admin_groups"]:
272 278 if group in user_attrs["groups"]:
273 279 user_attrs["admin"] = True
274 280 log.debug("Final crowd user object: \n%s" % (formatted_json(user_attrs)))
275 281 log.info('user %s authenticated correctly' % user_attrs['username'])
276 282 return user_attrs
@@ -1,163 +1,165 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 RhodeCode authentication plugin for Jasig CAS
23 23 http://www.jasig.org/cas
24 24 """
25 25
26 26
27 27 import colander
28 28 import logging
29 29 import rhodecode
30 30 import urllib
31 31 import urllib2
32 32
33 33 from pylons.i18n.translation import lazy_ugettext as _
34 34 from sqlalchemy.ext.hybrid import hybrid_property
35 35
36 36 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
37 37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
38 38 from rhodecode.authentication.routes import AuthnPluginResourceBase
39 from rhodecode.lib.colander_utils import strip_whitespace
39 40 from rhodecode.lib.utils2 import safe_unicode
40 41 from rhodecode.model.db import User
41 42
42 43 log = logging.getLogger(__name__)
43 44
44 45
45 46 def plugin_factory(plugin_id, *args, **kwds):
46 47 """
47 48 Factory function that is called during plugin discovery.
48 49 It returns the plugin instance.
49 50 """
50 51 plugin = RhodeCodeAuthPlugin(plugin_id)
51 52 return plugin
52 53
53 54
54 55 class JasigCasAuthnResource(AuthnPluginResourceBase):
55 56 pass
56 57
57 58
58 59 class JasigCasSettingsSchema(AuthnPluginSettingsSchemaBase):
59 60 service_url = colander.SchemaNode(
60 61 colander.String(),
61 62 default='https://domain.com/cas/v1/tickets',
62 63 description=_('The url of the Jasig CAS REST service'),
64 preparer=strip_whitespace,
63 65 title=_('URL'),
64 66 widget='string')
65 67
66 68
67 69 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
68 70
69 71 def includeme(self, config):
70 72 config.add_authn_plugin(self)
71 73 config.add_authn_resource(self.get_id(), JasigCasAuthnResource(self))
72 74 config.add_view(
73 75 'rhodecode.authentication.views.AuthnPluginViewBase',
74 76 attr='settings_get',
75 77 request_method='GET',
76 78 route_name='auth_home',
77 79 context=JasigCasAuthnResource)
78 80 config.add_view(
79 81 'rhodecode.authentication.views.AuthnPluginViewBase',
80 82 attr='settings_post',
81 83 request_method='POST',
82 84 route_name='auth_home',
83 85 context=JasigCasAuthnResource)
84 86
85 87 def get_settings_schema(self):
86 88 return JasigCasSettingsSchema()
87 89
88 90 def get_display_name(self):
89 91 return _('Jasig-CAS')
90 92
91 93 @hybrid_property
92 94 def name(self):
93 95 return "jasig-cas"
94 96
95 97 @hybrid_property
96 98 def is_container_auth(self):
97 99 return True
98 100
99 101 def use_fake_password(self):
100 102 return True
101 103
102 104 def user_activation_state(self):
103 105 def_user_perms = User.get_default_user().AuthUser.permissions['global']
104 106 return 'hg.extern_activate.auto' in def_user_perms
105 107
106 108 def auth(self, userobj, username, password, settings, **kwargs):
107 109 """
108 110 Given a user object (which may be null), username, a plaintext password,
109 111 and a settings object (containing all the keys needed as listed in settings()),
110 112 authenticate this user's login attempt.
111 113
112 114 Return None on failure. On success, return a dictionary of the form:
113 115
114 116 see: RhodeCodeAuthPluginBase.auth_func_attrs
115 117 This is later validated for correctness
116 118 """
117 119 if not username or not password:
118 120 log.debug('Empty username or password skipping...')
119 121 return None
120 122
121 123 log.debug("Jasig CAS settings: %s", settings)
122 124 params = urllib.urlencode({'username': username, 'password': password})
123 125 headers = {"Content-type": "application/x-www-form-urlencoded",
124 126 "Accept": "text/plain",
125 127 "User-Agent": "RhodeCode-auth-%s" % rhodecode.__version__}
126 128 url = settings["service_url"]
127 129
128 130 log.debug("Sent Jasig CAS: \n%s",
129 131 {"url": url, "body": params, "headers": headers})
130 132 request = urllib2.Request(url, params, headers)
131 133 try:
132 134 response = urllib2.urlopen(request)
133 135 except urllib2.HTTPError as e:
134 136 log.debug("HTTPError when requesting Jasig CAS (status code: %d)" % e.code)
135 137 return None
136 138 except urllib2.URLError as e:
137 139 log.debug("URLError when requesting Jasig CAS url: %s " % url)
138 140 return None
139 141
140 142 # old attrs fetched from RhodeCode database
141 143 admin = getattr(userobj, 'admin', False)
142 144 active = getattr(userobj, 'active', True)
143 145 email = getattr(userobj, 'email', '')
144 146 username = getattr(userobj, 'username', username)
145 147 firstname = getattr(userobj, 'firstname', '')
146 148 lastname = getattr(userobj, 'lastname', '')
147 149 extern_type = getattr(userobj, 'extern_type', '')
148 150
149 151 user_attrs = {
150 152 'username': username,
151 153 'firstname': safe_unicode(firstname or username),
152 154 'lastname': safe_unicode(lastname or ''),
153 155 'groups': [],
154 156 'email': email or '',
155 157 'admin': admin or False,
156 158 'active': active,
157 159 'active_from_extern': True,
158 160 'extern_name': username,
159 161 'extern_type': extern_type,
160 162 }
161 163
162 164 log.info('user %s authenticated correctly' % user_attrs['username'])
163 165 return user_attrs
@@ -1,447 +1,458 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 RhodeCode authentication plugin for LDAP
23 23 """
24 24
25 25
26 26 import colander
27 27 import logging
28 28 import traceback
29 29
30 30 from pylons.i18n.translation import lazy_ugettext as _
31 31 from sqlalchemy.ext.hybrid import hybrid_property
32 32
33 33 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
34 34 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
35 35 from rhodecode.authentication.routes import AuthnPluginResourceBase
36 from rhodecode.lib.colander_utils import strip_whitespace
36 37 from rhodecode.lib.exceptions import (
37 38 LdapConnectionError, LdapUsernameError, LdapPasswordError, LdapImportError
38 39 )
39 40 from rhodecode.lib.utils2 import safe_unicode, safe_str
40 41 from rhodecode.model.db import User
41 42 from rhodecode.model.validators import Missing
42 43
43 44 log = logging.getLogger(__name__)
44 45
45 46 try:
46 47 import ldap
47 48 except ImportError:
48 49 # means that python-ldap is not installed
49 50 ldap = Missing()
50 51
51 52
52 53 def plugin_factory(plugin_id, *args, **kwds):
53 54 """
54 55 Factory function that is called during plugin discovery.
55 56 It returns the plugin instance.
56 57 """
57 58 plugin = RhodeCodeAuthPlugin(plugin_id)
58 59 return plugin
59 60
60 61
61 62 class LdapAuthnResource(AuthnPluginResourceBase):
62 63 pass
63 64
64 65
65 66 class LdapSettingsSchema(AuthnPluginSettingsSchemaBase):
66 67 tls_kind_choices = ['PLAIN', 'LDAPS', 'START_TLS']
67 68 tls_reqcert_choices = ['NEVER', 'ALLOW', 'TRY', 'DEMAND', 'HARD']
68 69 search_scope_choices = ['BASE', 'ONELEVEL', 'SUBTREE']
69 70
70 71 host = colander.SchemaNode(
71 72 colander.String(),
72 73 default='',
73 74 description=_('Host of the LDAP Server'),
75 preparer=strip_whitespace,
74 76 title=_('LDAP Host'),
75 77 widget='string')
76 78 port = colander.SchemaNode(
77 79 colander.Int(),
78 80 default=389,
79 81 description=_('Port that the LDAP server is listening on'),
82 preparer=strip_whitespace,
80 83 title=_('Port'),
81 84 validator=colander.Range(min=0, max=65536),
82 85 widget='int')
83 86 dn_user = colander.SchemaNode(
84 87 colander.String(),
85 88 default='',
86 89 description=_('User to connect to LDAP'),
87 90 missing='',
91 preparer=strip_whitespace,
88 92 title=_('Account'),
89 93 widget='string')
90 94 dn_pass = colander.SchemaNode(
91 95 colander.String(),
92 96 default='',
93 97 description=_('Password to connect to LDAP'),
94 98 missing='',
99 preparer=strip_whitespace,
95 100 title=_('Password'),
96 101 widget='password')
97 102 tls_kind = colander.SchemaNode(
98 103 colander.String(),
99 104 default=tls_kind_choices[0],
100 105 description=_('TLS Type'),
101 106 title=_('Connection Security'),
102 107 validator=colander.OneOf(tls_kind_choices),
103 108 widget='select')
104 109 tls_reqcert = colander.SchemaNode(
105 110 colander.String(),
106 111 default=tls_reqcert_choices[0],
107 112 description=_('Require Cert over TLS?'),
108 113 title=_('Certificate Checks'),
109 114 validator=colander.OneOf(tls_reqcert_choices),
110 115 widget='select')
111 116 base_dn = colander.SchemaNode(
112 117 colander.String(),
113 118 default='',
114 119 description=_('Base DN to search (e.g., dc=mydomain,dc=com)'),
115 120 missing='',
121 preparer=strip_whitespace,
116 122 title=_('Base DN'),
117 123 widget='string')
118 124 filter = colander.SchemaNode(
119 125 colander.String(),
120 126 default='',
121 127 description=_('Filter to narrow results (e.g., ou=Users, etc)'),
122 128 missing='',
129 preparer=strip_whitespace,
123 130 title=_('LDAP Search Filter'),
124 131 widget='string')
125 132 search_scope = colander.SchemaNode(
126 133 colander.String(),
127 134 default=search_scope_choices[0],
128 135 description=_('How deep to search LDAP'),
129 136 title=_('LDAP Search Scope'),
130 137 validator=colander.OneOf(search_scope_choices),
131 138 widget='select')
132 139 attr_login = colander.SchemaNode(
133 140 colander.String(),
134 141 default='',
135 142 description=_('LDAP Attribute to map to user name'),
143 missing_msg=_('The LDAP Login attribute of the CN must be specified'),
144 preparer=strip_whitespace,
136 145 title=_('Login Attribute'),
137 missing_msg=_('The LDAP Login attribute of the CN must be specified'),
138 146 widget='string')
139 147 attr_firstname = colander.SchemaNode(
140 148 colander.String(),
141 149 default='',
142 150 description=_('LDAP Attribute to map to first name'),
143 151 missing='',
152 preparer=strip_whitespace,
144 153 title=_('First Name Attribute'),
145 154 widget='string')
146 155 attr_lastname = colander.SchemaNode(
147 156 colander.String(),
148 157 default='',
149 158 description=_('LDAP Attribute to map to last name'),
150 159 missing='',
160 preparer=strip_whitespace,
151 161 title=_('Last Name Attribute'),
152 162 widget='string')
153 163 attr_email = colander.SchemaNode(
154 164 colander.String(),
155 165 default='',
156 166 description=_('LDAP Attribute to map to email address'),
157 167 missing='',
168 preparer=strip_whitespace,
158 169 title=_('Email Attribute'),
159 170 widget='string')
160 171
161 172
162 173 class AuthLdap(object):
163 174
164 175 def _build_servers(self):
165 176 return ', '.join(
166 177 ["{}://{}:{}".format(
167 178 self.ldap_server_type, host.strip(), self.LDAP_SERVER_PORT)
168 179 for host in self.SERVER_ADDRESSES])
169 180
170 181 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
171 182 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
172 183 search_scope='SUBTREE', attr_login='uid',
173 184 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))'):
174 185 if isinstance(ldap, Missing):
175 186 raise LdapImportError("Missing or incompatible ldap library")
176 187
177 188 self.ldap_version = ldap_version
178 189 self.ldap_server_type = 'ldap'
179 190
180 191 self.TLS_KIND = tls_kind
181 192
182 193 if self.TLS_KIND == 'LDAPS':
183 194 port = port or 689
184 195 self.ldap_server_type += 's'
185 196
186 197 OPT_X_TLS_DEMAND = 2
187 198 self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert,
188 199 OPT_X_TLS_DEMAND)
189 200 # split server into list
190 201 self.SERVER_ADDRESSES = server.split(',')
191 202 self.LDAP_SERVER_PORT = port
192 203
193 204 # USE FOR READ ONLY BIND TO LDAP SERVER
194 205 self.attr_login = attr_login
195 206
196 207 self.LDAP_BIND_DN = safe_str(bind_dn)
197 208 self.LDAP_BIND_PASS = safe_str(bind_pass)
198 209 self.LDAP_SERVER = self._build_servers()
199 210 self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
200 211 self.BASE_DN = safe_str(base_dn)
201 212 self.LDAP_FILTER = safe_str(ldap_filter)
202 213
203 214 def _get_ldap_server(self):
204 215 if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
205 216 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
206 217 '/etc/openldap/cacerts')
207 218 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
208 219 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
209 220 ldap.set_option(ldap.OPT_TIMEOUT, 20)
210 221 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
211 222 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
212 223 if self.TLS_KIND != 'PLAIN':
213 224 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
214 225 server = ldap.initialize(self.LDAP_SERVER)
215 226 if self.ldap_version == 2:
216 227 server.protocol = ldap.VERSION2
217 228 else:
218 229 server.protocol = ldap.VERSION3
219 230
220 231 if self.TLS_KIND == 'START_TLS':
221 232 server.start_tls_s()
222 233
223 234 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
224 235 log.debug('Trying simple_bind with password and given DN: %s',
225 236 self.LDAP_BIND_DN)
226 237 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
227 238
228 239 return server
229 240
230 241 def get_uid(self, username):
231 242 from rhodecode.lib.helpers import chop_at
232 243 uid = username
233 244 for server_addr in self.SERVER_ADDRESSES:
234 245 uid = chop_at(username, "@%s" % server_addr)
235 246 return uid
236 247
237 248 def fetch_attrs_from_simple_bind(self, server, dn, username, password):
238 249 try:
239 250 log.debug('Trying simple bind with %s', dn)
240 251 server.simple_bind_s(dn, safe_str(password))
241 252 user = server.search_ext_s(
242 253 dn, ldap.SCOPE_BASE, '(objectClass=*)', )[0]
243 254 _, attrs = user
244 255 return attrs
245 256
246 257 except ldap.INVALID_CREDENTIALS:
247 258 log.debug(
248 259 "LDAP rejected password for user '%s': %s, org_exc:",
249 260 username, dn, exc_info=True)
250 261
251 262 def authenticate_ldap(self, username, password):
252 263 """
253 264 Authenticate a user via LDAP and return his/her LDAP properties.
254 265
255 266 Raises AuthenticationError if the credentials are rejected, or
256 267 EnvironmentError if the LDAP server can't be reached.
257 268
258 269 :param username: username
259 270 :param password: password
260 271 """
261 272
262 273 uid = self.get_uid(username)
263 274
264 275 if not password:
265 276 msg = "Authenticating user %s with blank password not allowed"
266 277 log.warning(msg, username)
267 278 raise LdapPasswordError(msg)
268 279 if "," in username:
269 280 raise LdapUsernameError("invalid character in username: ,")
270 281 try:
271 282 server = self._get_ldap_server()
272 283 filter_ = '(&%s(%s=%s))' % (
273 284 self.LDAP_FILTER, self.attr_login, username)
274 285 log.debug("Authenticating %r filter %s at %s", self.BASE_DN,
275 286 filter_, self.LDAP_SERVER)
276 287 lobjects = server.search_ext_s(
277 288 self.BASE_DN, self.SEARCH_SCOPE, filter_)
278 289
279 290 if not lobjects:
280 291 raise ldap.NO_SUCH_OBJECT()
281 292
282 293 for (dn, _attrs) in lobjects:
283 294 if dn is None:
284 295 continue
285 296
286 297 user_attrs = self.fetch_attrs_from_simple_bind(
287 298 server, dn, username, password)
288 299 if user_attrs:
289 300 break
290 301
291 302 else:
292 303 log.debug("No matching LDAP objects for authentication "
293 304 "of '%s' (%s)", uid, username)
294 305 raise LdapPasswordError('Failed to authenticate user '
295 306 'with given password')
296 307
297 308 except ldap.NO_SUCH_OBJECT:
298 309 log.debug("LDAP says no such user '%s' (%s), org_exc:",
299 310 uid, username, exc_info=True)
300 311 raise LdapUsernameError()
301 312 except ldap.SERVER_DOWN:
302 313 org_exc = traceback.format_exc()
303 314 raise LdapConnectionError(
304 315 "LDAP can't access authentication "
305 316 "server, org_exc:%s" % org_exc)
306 317
307 318 return dn, user_attrs
308 319
309 320
310 321 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
311 322 # used to define dynamic binding in the
312 323 DYNAMIC_BIND_VAR = '$login'
313 324
314 325 def includeme(self, config):
315 326 config.add_authn_plugin(self)
316 327 config.add_authn_resource(self.get_id(), LdapAuthnResource(self))
317 328 config.add_view(
318 329 'rhodecode.authentication.views.AuthnPluginViewBase',
319 330 attr='settings_get',
320 331 request_method='GET',
321 332 route_name='auth_home',
322 333 context=LdapAuthnResource)
323 334 config.add_view(
324 335 'rhodecode.authentication.views.AuthnPluginViewBase',
325 336 attr='settings_post',
326 337 request_method='POST',
327 338 route_name='auth_home',
328 339 context=LdapAuthnResource)
329 340
330 341 def get_settings_schema(self):
331 342 return LdapSettingsSchema()
332 343
333 344 def get_display_name(self):
334 345 return _('LDAP')
335 346
336 347 @hybrid_property
337 348 def name(self):
338 349 return "ldap"
339 350
340 351 def use_fake_password(self):
341 352 return True
342 353
343 354 def user_activation_state(self):
344 355 def_user_perms = User.get_default_user().AuthUser.permissions['global']
345 356 return 'hg.extern_activate.auto' in def_user_perms
346 357
347 358 def try_dynamic_binding(self, username, password, current_args):
348 359 """
349 360 Detects marker inside our original bind, and uses dynamic auth if
350 361 present
351 362 """
352 363
353 364 org_bind = current_args['bind_dn']
354 365 passwd = current_args['bind_pass']
355 366
356 367 def has_bind_marker(username):
357 368 if self.DYNAMIC_BIND_VAR in username:
358 369 return True
359 370
360 371 # we only passed in user with "special" variable
361 372 if org_bind and has_bind_marker(org_bind) and not passwd:
362 373 log.debug('Using dynamic user/password binding for ldap '
363 374 'authentication. Replacing `%s` with username',
364 375 self.DYNAMIC_BIND_VAR)
365 376 current_args['bind_dn'] = org_bind.replace(
366 377 self.DYNAMIC_BIND_VAR, username)
367 378 current_args['bind_pass'] = password
368 379
369 380 return current_args
370 381
371 382 def auth(self, userobj, username, password, settings, **kwargs):
372 383 """
373 384 Given a user object (which may be null), username, a plaintext password,
374 385 and a settings object (containing all the keys needed as listed in
375 386 settings()), authenticate this user's login attempt.
376 387
377 388 Return None on failure. On success, return a dictionary of the form:
378 389
379 390 see: RhodeCodeAuthPluginBase.auth_func_attrs
380 391 This is later validated for correctness
381 392 """
382 393
383 394 if not username or not password:
384 395 log.debug('Empty username or password skipping...')
385 396 return None
386 397
387 398 ldap_args = {
388 399 'server': settings.get('host', ''),
389 400 'base_dn': settings.get('base_dn', ''),
390 401 'port': settings.get('port'),
391 402 'bind_dn': settings.get('dn_user'),
392 403 'bind_pass': settings.get('dn_pass'),
393 404 'tls_kind': settings.get('tls_kind'),
394 405 'tls_reqcert': settings.get('tls_reqcert'),
395 406 'search_scope': settings.get('search_scope'),
396 407 'attr_login': settings.get('attr_login'),
397 408 'ldap_version': 3,
398 409 'ldap_filter': settings.get('filter'),
399 410 }
400 411
401 412 ldap_attrs = self.try_dynamic_binding(username, password, ldap_args)
402 413
403 414 log.debug('Checking for ldap authentication.')
404 415
405 416 try:
406 417 aldap = AuthLdap(**ldap_args)
407 418 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, password)
408 419 log.debug('Got ldap DN response %s', user_dn)
409 420
410 421 def get_ldap_attr(k):
411 422 return ldap_attrs.get(settings.get(k), [''])[0]
412 423
413 424 # old attrs fetched from RhodeCode database
414 425 admin = getattr(userobj, 'admin', False)
415 426 active = getattr(userobj, 'active', True)
416 427 email = getattr(userobj, 'email', '')
417 428 username = getattr(userobj, 'username', username)
418 429 firstname = getattr(userobj, 'firstname', '')
419 430 lastname = getattr(userobj, 'lastname', '')
420 431 extern_type = getattr(userobj, 'extern_type', '')
421 432
422 433 groups = []
423 434 user_attrs = {
424 435 'username': username,
425 436 'firstname': safe_unicode(
426 437 get_ldap_attr('attr_firstname') or firstname),
427 438 'lastname': safe_unicode(
428 439 get_ldap_attr('attr_lastname') or lastname),
429 440 'groups': groups,
430 441 'email': get_ldap_attr('attr_email' or email),
431 442 'admin': admin,
432 443 'active': active,
433 444 "active_from_extern": None,
434 445 'extern_name': user_dn,
435 446 'extern_type': extern_type,
436 447 }
437 448 log.debug('ldap user: %s', user_attrs)
438 449 log.info('user %s authenticated correctly', user_attrs['username'])
439 450
440 451 return user_attrs
441 452
442 453 except (LdapUsernameError, LdapPasswordError, LdapImportError):
443 454 log.exception("LDAP related exception")
444 455 return None
445 456 except (Exception,):
446 457 log.exception("Other exception")
447 458 return None
@@ -1,155 +1,158 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 """
21 21 RhodeCode authentication library for PAM
22 22 """
23 23
24 24 import colander
25 25 import grp
26 26 import logging
27 27 import pam
28 28 import pwd
29 29 import re
30 30 import socket
31 31
32 32 from pylons.i18n.translation import lazy_ugettext as _
33 33 from sqlalchemy.ext.hybrid import hybrid_property
34 34
35 35 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
36 36 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
37 37 from rhodecode.authentication.routes import AuthnPluginResourceBase
38 from rhodecode.lib.colander_utils import strip_whitespace
38 39
39 40 log = logging.getLogger(__name__)
40 41
41 42
42 43 def plugin_factory(plugin_id, *args, **kwds):
43 44 """
44 45 Factory function that is called during plugin discovery.
45 46 It returns the plugin instance.
46 47 """
47 48 plugin = RhodeCodeAuthPlugin(plugin_id)
48 49 return plugin
49 50
50 51
51 52 class PamAuthnResource(AuthnPluginResourceBase):
52 53 pass
53 54
54 55
55 56 class PamSettingsSchema(AuthnPluginSettingsSchemaBase):
56 57 service = colander.SchemaNode(
57 58 colander.String(),
58 59 default='login',
59 60 description=_('PAM service name to use for authentication.'),
61 preparer=strip_whitespace,
60 62 title=_('PAM service name'),
61 63 widget='string')
62 64 gecos = colander.SchemaNode(
63 65 colander.String(),
64 66 default='(?P<last_name>.+),\s*(?P<first_name>\w+)',
65 67 description=_('Regular expression for extracting user name/email etc. '
66 68 'from Unix userinfo.'),
69 preparer=strip_whitespace,
67 70 title=_('Gecos Regex'),
68 71 widget='string')
69 72
70 73
71 74 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
72 75 # PAM authentication can be slow. Repository operations involve a lot of
73 76 # auth calls. Little caching helps speedup push/pull operations significantly
74 77 AUTH_CACHE_TTL = 4
75 78
76 79 def includeme(self, config):
77 80 config.add_authn_plugin(self)
78 81 config.add_authn_resource(self.get_id(), PamAuthnResource(self))
79 82 config.add_view(
80 83 'rhodecode.authentication.views.AuthnPluginViewBase',
81 84 attr='settings_get',
82 85 request_method='GET',
83 86 route_name='auth_home',
84 87 context=PamAuthnResource)
85 88 config.add_view(
86 89 'rhodecode.authentication.views.AuthnPluginViewBase',
87 90 attr='settings_post',
88 91 request_method='POST',
89 92 route_name='auth_home',
90 93 context=PamAuthnResource)
91 94
92 95 def get_display_name(self):
93 96 return _('PAM')
94 97
95 98 @hybrid_property
96 99 def name(self):
97 100 return "pam"
98 101
99 102 def get_settings_schema(self):
100 103 return PamSettingsSchema()
101 104
102 105 def use_fake_password(self):
103 106 return True
104 107
105 108 def auth(self, userobj, username, password, settings, **kwargs):
106 109 if not username or not password:
107 110 log.debug('Empty username or password skipping...')
108 111 return None
109 112
110 113 auth_result = pam.authenticate(username, password, settings["service"])
111 114
112 115 if not auth_result:
113 116 log.error("PAM was unable to authenticate user: %s" % (username, ))
114 117 return None
115 118
116 119 log.debug('Got PAM response %s' % (auth_result, ))
117 120
118 121 # old attrs fetched from RhodeCode database
119 122 default_email = "%s@%s" % (username, socket.gethostname())
120 123 admin = getattr(userobj, 'admin', False)
121 124 active = getattr(userobj, 'active', True)
122 125 email = getattr(userobj, 'email', '') or default_email
123 126 username = getattr(userobj, 'username', username)
124 127 firstname = getattr(userobj, 'firstname', '')
125 128 lastname = getattr(userobj, 'lastname', '')
126 129 extern_type = getattr(userobj, 'extern_type', '')
127 130
128 131 user_attrs = {
129 132 'username': username,
130 133 'firstname': firstname,
131 134 'lastname': lastname,
132 135 'groups': [g.gr_name for g in grp.getgrall()
133 136 if username in g.gr_mem],
134 137 'email': email,
135 138 'admin': admin,
136 139 'active': active,
137 140 'active_from_extern': None,
138 141 'extern_name': username,
139 142 'extern_type': extern_type,
140 143 }
141 144
142 145 try:
143 146 user_data = pwd.getpwnam(username)
144 147 regex = settings["gecos"]
145 148 match = re.search(regex, user_data.pw_gecos)
146 149 if match:
147 150 user_attrs["firstname"] = match.group('first_name')
148 151 user_attrs["lastname"] = match.group('last_name')
149 152 except Exception:
150 153 log.warning("Cannot extract additional info for PAM user")
151 154 pass
152 155
153 156 log.debug("pamuser: %s", user_attrs)
154 157 log.info('user %s authenticated correctly' % user_attrs['username'])
155 158 return user_attrs
General Comments 0
You need to be logged in to leave comments. Login now