]> WPIA git - gigi.git/blob - src/club/wpia/gigi/pages/LoginPage.java
add: ensure that for Org Administrator actions certificate login is used
[gigi.git] / src / club / wpia / gigi / pages / LoginPage.java
1 package club.wpia.gigi.pages;
2
3 import static club.wpia.gigi.Gigi.*;
4
5 import java.io.IOException;
6 import java.io.PrintWriter;
7 import java.math.BigInteger;
8 import java.security.cert.X509Certificate;
9 import java.util.Date;
10 import java.util.Map;
11
12 import javax.servlet.http.HttpServletRequest;
13 import javax.servlet.http.HttpServletResponse;
14 import javax.servlet.http.HttpSession;
15
16 import club.wpia.gigi.GigiApiException;
17 import club.wpia.gigi.database.GigiPreparedStatement;
18 import club.wpia.gigi.database.GigiResultSet;
19 import club.wpia.gigi.dbObjects.Certificate;
20 import club.wpia.gigi.dbObjects.CertificateOwner;
21 import club.wpia.gigi.dbObjects.Group;
22 import club.wpia.gigi.dbObjects.User;
23 import club.wpia.gigi.localisation.Language;
24 import club.wpia.gigi.output.template.Form;
25 import club.wpia.gigi.output.template.TranslateCommand;
26 import club.wpia.gigi.pages.main.RegisterPage;
27 import club.wpia.gigi.util.AuthorizationContext;
28 import club.wpia.gigi.util.PasswordHash;
29 import club.wpia.gigi.util.RateLimit;
30 import club.wpia.gigi.util.RateLimit.RateLimitException;
31 import club.wpia.gigi.util.ServerConstants;
32 import club.wpia.gigi.util.ServerConstants.Host;
33
34 public class LoginPage extends Page {
35
36     public static final RateLimit RATE_LIMIT = new RateLimit(10, 5 * 60 * 1000);
37
38     public class LoginForm extends Form {
39
40         public LoginForm(HttpServletRequest hsr) {
41             super(hsr);
42         }
43
44         @Override
45         public RedirectResult submit(HttpServletRequest req) throws GigiApiException {
46             if (RegisterPage.RATE_LIMIT.isLimitExceeded(req.getRemoteAddr())) {
47                 throw new RateLimitException();
48             }
49             tryAuthWithUnpw(req);
50             return new RedirectResult(redirectPath(req));
51         }
52
53         @Override
54         protected void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
55             getDefaultTemplate().output(out, l, vars);
56         }
57
58     }
59
60     public static final String LOGIN_RETURNPATH = "login-returnpath";
61
62     public LoginPage() {
63         super("Password Login");
64     }
65
66     @Override
67     public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
68         if (req.getHeader("Host").equals(ServerConstants.getHostNamePortSecure(Host.SECURE))) {
69             resp.getWriter().println(getLanguage(req).getTranslation("Authentication with certificate failed. Try another certificate or use a password."));
70         } else {
71             new LoginForm(req).output(resp.getWriter(), getLanguage(req), getDefaultVars(req));
72         }
73     }
74
75     @Override
76     public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
77         if (Form.printFormErrors(req, resp.getWriter())) {
78             Form.getForm(req, LoginForm.class).output(resp.getWriter(), getLanguage(req), getDefaultVars(req));
79         }
80     }
81
82     @Override
83     public boolean beforeTemplate(HttpServletRequest req, HttpServletResponse resp) throws IOException {
84         if (req.getSession().getAttribute("loggedin") == null) {
85             X509Certificate cert = getCertificateFromRequest(req);
86             if (cert != null) {
87                 tryAuthWithCertificate(req, cert);
88             }
89             if (req.getMethod().equals("POST")) {
90                 return Form.getForm(req, LoginForm.class).submitExceptionProtected(req, resp);
91             }
92         }
93
94         if (req.getSession().getAttribute("loggedin") != null) {
95             resp.sendRedirect(redirectPath(req));
96             return true;
97         }
98         return false;
99     }
100
101     private static String redirectPath(HttpServletRequest req) {
102         String redir = (String) req.getAttribute(LOGIN_RETURNPATH);
103         String s = redir;
104         if (s != null) {
105             if ( !s.startsWith("/")) {
106                 s = "/" + s;
107             }
108             return s;
109         } else {
110             return "/";
111         }
112     }
113
114     @Override
115     public boolean needsLogin() {
116         return false;
117     }
118
119     private void tryAuthWithUnpw(HttpServletRequest req) throws GigiApiException {
120         String un = req.getParameter("username");
121         String pw = req.getParameter("password");
122         try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT `password`, `id` FROM `users` WHERE `email`=? AND verified='1'")) {
123             ps.setString(1, un);
124             GigiResultSet rs = ps.executeQuery();
125             if ( !rs.next()) {
126                 throw new GigiApiException("Username and password didn't match.");
127             }
128
129             User user = User.getById(rs.getInt(2));
130             if (user == null) {
131                 throw new GigiApiException("Username and password didn't match.");
132             }
133
134             String dbHash = rs.getString(1);
135             String hash = PasswordHash.verifyHash(pw, dbHash);
136             if (hash != null) {
137                 if ( !hash.equals(dbHash)) {
138                     try (GigiPreparedStatement gps = new GigiPreparedStatement("UPDATE `users` SET `password`=? WHERE `email`=?")) {
139                         gps.setString(1, hash);
140                         gps.setString(2, un);
141                         gps.executeUpdate();
142                     }
143                 }
144
145                 loginSession(req, user, false);
146                 req.getSession().setAttribute(LOGIN_METHOD, new TranslateCommand("Password"));
147                 return;
148             }
149         }
150     }
151
152     public static User getUser(HttpServletRequest req) {
153         AuthorizationContext ac = getAuthorizationContext(req);
154         if (ac == null) {
155             return null;
156         }
157         return ac.getActor();
158     }
159
160     public static AuthorizationContext getAuthorizationContext(HttpServletRequest req) {
161         return ((AuthorizationContext) req.getSession().getAttribute(AUTH_CONTEXT));
162     }
163
164     private void tryAuthWithCertificate(HttpServletRequest req, X509Certificate x509Certificate) {
165         BigInteger serial = extractSerialFormCert(x509Certificate);
166         Certificate c = Certificate.getBySerial(serial);
167         User user = fetchUserBySerial(serial);
168         if (user == null) {
169             return;
170         }
171         if (c.getExpiryDate().before(new Date()) || c.getRevocationDate() != null || c.isLoginEnabled() == false) {
172             return;
173         }
174         loginSession(req, user, true);
175         req.getSession().setAttribute(CERT_SERIAL, serial);
176         req.getSession().setAttribute(CERT_ISSUER, x509Certificate.getIssuerDN());
177         req.getSession().setAttribute(LOGIN_METHOD, new TranslateCommand("Certificate"));
178     }
179
180     public static BigInteger extractSerialFormCert(X509Certificate x509Certificate) {
181         return x509Certificate.getSerialNumber();
182     }
183
184     public static User fetchUserBySerial(BigInteger serial) {
185         CertificateOwner o = CertificateOwner.getByEnabledSerial(serial);
186         if (o == null || !(o instanceof User)) {
187             return null;
188         }
189         return (User) o;
190     }
191
192     public static X509Certificate getCertificateFromRequest(HttpServletRequest req) {
193         X509Certificate[] cert = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
194         X509Certificate uc = null;
195         if (cert != null && cert[0] != null) {
196             uc = cert[0];
197         }
198         return uc;
199     }
200
201     private static final Group LOGIN_BLOCKED = Group.BLOCKED_LOGIN;
202
203     private void loginSession(HttpServletRequest req, User user, boolean isStronglyAuthenticated) {
204         if (user.isInGroup(LOGIN_BLOCKED)) {
205             return;
206         }
207         req.setAttribute(LOGIN_RETURNPATH, req.getSession().getAttribute(LOGIN_RETURNPATH));
208         req.getSession().invalidate();
209         HttpSession hs = req.getSession();
210         hs.setAttribute(LOGGEDIN, true);
211         hs.setAttribute(Language.SESSION_ATTRIB_NAME, user.getPreferredLocale());
212         hs.setAttribute(AUTH_CONTEXT, new AuthorizationContext(user, user, isStronglyAuthenticated));
213     }
214
215     @Override
216     public boolean isPermitted(AuthorizationContext ac) {
217         return ac == null;
218     }
219 }