Prefix userdata hives with _ to avoid conflicts
[cascardo/ipsilon.git] / ipsilon / login / authldap.py
1 # Copyright (C) 2014  Ipsilon Contributors, see COPYING for license
2
3 from ipsilon.login.common import LoginFormBase, LoginManagerBase
4 from ipsilon.util.plugin import PluginObject
5 from ipsilon.util.log import Log
6 from ipsilon.util import config as pconfig
7 from ipsilon.info.infoldap import InfoProvider as LDAPInfo
8 import ldap
9
10
11 class LDAP(LoginFormBase, Log):
12
13     def __init__(self, site, mgr, page):
14         super(LDAP, self).__init__(site, mgr, page)
15         self.ldap_info = None
16
17     def _ldap_connect(self):
18
19         tls = self.lm.tls.lower()
20         tls_req_opt = None
21         if tls == "never":
22             tls_req_opt = ldap.OPT_X_TLS_NEVER
23         elif tls == "demand":
24             tls_req_opt = ldap.OPT_X_TLS_DEMAND
25         elif tls == "allow":
26             tls_req_opt = ldap.OPT_X_TLS_ALLOW
27         elif tls == "try":
28             tls_req_opt = ldap.OPT_X_TLS_TRY
29         if tls_req_opt is not None:
30             ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, tls_req_opt)
31
32         conn = ldap.initialize(self.lm.server_url)
33
34         if tls != "notls":
35             if not self.lm.server_url.startswith("ldaps"):
36                 conn.start_tls_s()
37         return conn
38
39     def _authenticate(self, username, password):
40
41         conn = self._ldap_connect()
42         dn = self.lm.bind_dn_tmpl % {'username': username}
43         conn.simple_bind_s(dn, password)
44
45         # Bypass info plugins to optimize data retrieval
46         if self.lm.get_user_info:
47             self.lm.info = None
48
49             if not self.ldap_info:
50                 self.ldap_info = LDAPInfo(self._site)
51
52             return self.ldap_info.get_user_data_from_conn(conn, dn)
53
54         return None
55
56     def POST(self, *args, **kwargs):
57         username = kwargs.get("login_name")
58         password = kwargs.get("login_password")
59         userattrs = None
60         authed = False
61         errmsg = None
62
63         if username and password:
64             try:
65                 userattrs = self._authenticate(username, password)
66                 authed = True
67             except Exception, e:  # pylint: disable=broad-except
68                 errmsg = "Authentication failed"
69                 self.error("Exception raised: [%s]" % repr(e))
70         else:
71             errmsg = "Username or password is missing"
72             self.error(errmsg)
73
74         if authed:
75             return self.lm.auth_successful(self.trans, username, 'password',
76                                            userdata=userattrs)
77
78         context = self.create_tmpl_context(
79             username=username,
80             error=errmsg,
81             error_password=not password,
82             error_username=not username
83         )
84         # pylint: disable=star-args
85         return self._template('login/form.html', **context)
86
87
88 class LoginManager(LoginManagerBase):
89
90     def __init__(self, *args, **kwargs):
91         super(LoginManager, self).__init__(*args, **kwargs)
92         self.name = 'ldap'
93         self.path = 'ldap'
94         self.page = None
95         self.ldap_info = None
96         self.service_name = 'ldap'
97         self.description = """
98 Form based login Manager that uses a simple bind LDAP operation to perform
99 authentication. """
100         self.new_config(
101             self.name,
102             pconfig.String(
103                 'server url',
104                 'The LDAP server url.',
105                 'ldap://example.com'),
106             pconfig.Template(
107                 'bind dn template',
108                 'Template to turn username into DN.',
109                 'uid=%(username)s,ou=People,dc=example,dc=com'),
110             pconfig.Condition(
111                 'get user info',
112                 'Get user info via ldap using user credentials',
113                 True),
114             pconfig.Pick(
115                 'tls',
116                 'What TLS level show be required',
117                 ['Demand', 'Allow', 'Try', 'Never', 'NoTLS'],
118                 'Demand'),
119             pconfig.String(
120                 'username text',
121                 'Text used to ask for the username at login time.',
122                 'Username'),
123             pconfig.String(
124                 'password text',
125                 'Text used to ask for the password at login time.',
126                 'Password'),
127             pconfig.String(
128                 'help text',
129                 'Text used to guide the user at login time.',
130                 'Provide your Username and Password')
131         )
132
133     @property
134     def help_text(self):
135         return self.get_config_value('help text')
136
137     @property
138     def username_text(self):
139         return self.get_config_value('username text')
140
141     @property
142     def password_text(self):
143         return self.get_config_value('password text')
144
145     @property
146     def server_url(self):
147         return self.get_config_value('server url')
148
149     @property
150     def tls(self):
151         return self.get_config_value('tls')
152
153     @property
154     def get_user_info(self):
155         return self.get_config_value('get user info')
156
157     @property
158     def bind_dn_tmpl(self):
159         return self.get_config_value('bind dn template')
160
161     def get_tree(self, site):
162         self.page = LDAP(site, self, 'login/ldap')
163         return self.page
164
165
166 class Installer(object):
167
168     def __init__(self, *pargs):
169         self.name = 'ldap'
170         self.ptype = 'login'
171         self.pargs = pargs
172
173     def install_args(self, group):
174         group.add_argument('--ldap', choices=['yes', 'no'], default='no',
175                            help='Configure LDAP authentication')
176         group.add_argument('--ldap-server-url', action='store',
177                            help='LDAP Server Url')
178         group.add_argument('--ldap-bind-dn-template', action='store',
179                            help='LDAP Bind DN Template')
180
181     def configure(self, opts):
182         if opts['ldap'] != 'yes':
183             return
184
185         # Add configuration data to database
186         po = PluginObject(*self.pargs)
187         po.name = 'ldap'
188         po.wipe_data()
189         po.wipe_config_values()
190
191         config = dict()
192         if 'ldap_server_url' in opts:
193             config['server url'] = opts['ldap_server_url']
194         if 'ldap_bind_dn_template' in opts:
195             config['bind dn template'] = opts['ldap_bind_dn_template']
196         config['tls'] = 'Demand'
197         po.save_plugin_config(config)
198
199         # Update global config to add login plugin
200         po.is_enabled = True
201         po.save_enabled_state()