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