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