Return PAM errors from mod_intercept_form_submit
[cascardo/ipsilon.git] / ipsilon / login / authform.py
1 # Copyright (C) 2014 Ipsilon project Contributors, for license see COPYING
2
3 from ipsilon.login.common import LoginFormBase, LoginManagerBase, \
4     LoginManagerInstaller
5 from ipsilon.util.plugin import PluginObject
6 from ipsilon.util.user import UserSession
7 from ipsilon.util import config as pconfig
8 from string import Template
9 import cherrypy
10 import subprocess
11
12 # Translate PAM errors into more human-digestible values and eventually
13 # other languages.
14 PAM_AUTH_ERRORS = {
15     "Authentication token is no longer valid; new one required":
16         "Password is expired",
17     "Authentication failure":
18         "Authentication failure",
19 }
20
21
22 class Form(LoginFormBase):
23
24     def POST(self, *args, **kwargs):
25         us = UserSession()
26         us.remote_login()
27         user = us.get_user()
28         if not user.is_anonymous:
29             return self.lm.auth_successful(self.trans, user.name, 'password')
30         else:
31             error = cherrypy.request.wsgi_environ.get(
32                 'EXTERNAL_AUTH_ERROR',
33                 'Unknown error using external authentication'
34             )
35             error = PAM_AUTH_ERRORS.get(error, error)
36             cherrypy.log.error("Error: %s" % error)
37             return self.lm.auth_failed(self.trans, error)
38
39
40 class LoginManager(LoginManagerBase):
41
42     def __init__(self, *args, **kwargs):
43         super(LoginManager, self).__init__(*args, **kwargs)
44         self.name = 'form'
45         self.path = 'form'
46         self.page = None
47         self.service_name = 'form'
48         self.description = """
49 Form based login Manager. Relies on mod_intercept_form_submit plugin for
50  actual authentication. """
51         self.new_config(
52             self.name,
53             pconfig.String(
54                 'username text',
55                 'Text used to ask for the username at login time.',
56                 'Username'),
57             pconfig.String(
58                 'password text',
59                 'Text used to ask for the password at login time.',
60                 'Password'),
61             pconfig.String(
62                 'help text',
63                 'Text used to guide the user at login time.',
64                 'Insert your Username and Password and then submit.')
65         )
66
67     @property
68     def help_text(self):
69         return self.get_config_value('help text')
70
71     @property
72     def username_text(self):
73         return self.get_config_value('username text')
74
75     @property
76     def password_text(self):
77         return self.get_config_value('password text')
78
79     def get_tree(self, site):
80         self.page = Form(site, self, 'login/form')
81         return self.page
82
83
84 CONF_TEMPLATE = """
85 LoadModule intercept_form_submit_module modules/mod_intercept_form_submit.so
86 LoadModule authnz_pam_module modules/mod_authnz_pam.so
87
88 <Location /${instance}/login/form>
89   InterceptFormPAMService ${service}
90   InterceptFormLogin login_name
91   InterceptFormPassword login_password
92   # InterceptFormLoginSkip admin
93   # InterceptFormClearRemoteUserForSkipped on
94   InterceptFormPasswordRedact on
95 </Location>
96 """
97
98
99 class Installer(LoginManagerInstaller):
100
101     def __init__(self, *pargs):
102         super(Installer, self).__init__()
103         self.name = 'form'
104         self.pargs = pargs
105
106     def install_args(self, group):
107         group.add_argument('--form', choices=['yes', 'no'], default='no',
108                            help='Configure External Form authentication')
109         group.add_argument('--form-service', action='store', default='remote',
110                            help='PAM service name to use for authentication')
111
112     def configure(self, opts, changes):
113         if opts['form'] != 'yes':
114             return
115
116         confopts = {'instance': opts['instance'],
117                     'service': opts['form_service']}
118
119         tmpl = Template(CONF_TEMPLATE)
120         hunk = tmpl.substitute(**confopts)
121         with open(opts['httpd_conf'], 'a') as httpd_conf:
122             httpd_conf.write(hunk)
123
124         # Add configuration data to database
125         po = PluginObject(*self.pargs)
126         po.name = 'form'
127         po.wipe_data()
128         po.wipe_config_values()
129
130         # Update global config to add login plugin
131         po.is_enabled = True
132         po.save_enabled_state()
133
134         # for selinux enabled platforms, ignore if it fails just report
135         try:
136             subprocess.call(['/usr/sbin/setsebool', '-P',
137                              'httpd_mod_auth_pam=on'])
138         except Exception:  # pylint: disable=broad-except
139             pass