Browse Source

implemented:

- at_hash in id-token
- testing for strong passwords
- better protocol detection in PathHandler
- session extension on user activity

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
sqlite
Stephan Richter 3 months ago
parent
commit
d6007493df
  1. 4
      de.srsoftware.http/src/main/java/de/srsoftware/http/PathHandler.java
  2. 2
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/SessionService.java
  3. 2
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java
  4. 21
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/EmailController.java
  5. 117
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java
  6. 6
      de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java
  7. 4
      de.srsoftware.oidc.web/src/main/resources/en/todo.html

4
de.srsoftware.http/src/main/java/de/srsoftware/http/PathHandler.java

@ -10,6 +10,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; @@ -10,6 +10,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsExchange;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
@ -127,7 +128,8 @@ public abstract class PathHandler implements HttpHandler { @@ -127,7 +128,8 @@ public abstract class PathHandler implements HttpHandler {
var headers = ex.getRequestHeaders();
var host = headers.getFirst(FORWARDED_HOST);
if (host == null) host = headers.getFirst(HOST);
return host == null ? null : "https://" + host;
var proto = nullable(headers.getFirst("X-forwarded-proto")).orElseGet(() -> ex instanceof HttpsExchange ? "https" : "http");
return host == null ? null : proto + "://" + host;
}
public static JSONObject json(HttpExchange ex) throws IOException {

2
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/SessionService.java

@ -9,7 +9,7 @@ import java.util.Optional; @@ -9,7 +9,7 @@ import java.util.Optional;
public interface SessionService {
Session createSession(User user);
SessionService dropSession(String sessionId);
Session extend(String sessionId);
Session extend(Session session);
Optional<Session> retrieve(String sessionId);
SessionService setDuration(Duration duration);
}

2
de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/ClientController.java

@ -110,6 +110,7 @@ public class ClientController extends Controller { @@ -110,6 +110,7 @@ public class ClientController extends Controller {
// post-login paths
var session = optSession.get();
sessions.extend(session);
switch (path) {
case "/":
return deleteClient(ex, session);
@ -125,6 +126,7 @@ public class ClientController extends Controller { @@ -125,6 +126,7 @@ public class ClientController extends Controller {
// post-login paths
var session = optSession.get();
sessions.extend(session);
switch (path) {
case "/":
return load(ex, session);

21
de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/EmailController.java

@ -9,7 +9,7 @@ import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; @@ -9,7 +9,7 @@ import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.oidc.api.MailConfig;
import de.srsoftware.oidc.api.SessionService;
import de.srsoftware.oidc.api.data.User;
import de.srsoftware.oidc.api.data.Session;
import java.io.IOException;
public class EmailController extends Controller {
@ -24,10 +24,11 @@ public class EmailController extends Controller { @@ -24,10 +24,11 @@ public class EmailController extends Controller {
public boolean doGet(String path, HttpExchange ex) throws IOException {
var optSession = getSession(ex);
if (optSession.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
var user = optSession.get().user();
var session = optSession.get();
sessions.extend(session);
switch (path) {
case "/settings":
return provideSettings(ex, user);
return provideSettings(ex, session);
}
return notFound(ex);
}
@ -36,21 +37,23 @@ public class EmailController extends Controller { @@ -36,21 +37,23 @@ public class EmailController extends Controller {
public boolean doPost(String path, HttpExchange ex) throws IOException {
var optSession = getSession(ex);
if (optSession.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED, ex);
var user = optSession.get().user();
var session = optSession.get();
sessions.extend(session);
switch (path) {
case "/settings":
return saveSettings(ex, user);
return saveSettings(ex, session);
}
return notFound(ex);
}
private boolean provideSettings(HttpExchange ex, User user) throws IOException {
if (!user.hasPermission(MANAGE_SMTP)) return sendEmptyResponse(HTTP_FORBIDDEN, ex);
private boolean provideSettings(HttpExchange ex, Session session) throws IOException {
if (!session.user().hasPermission(MANAGE_SMTP)) return sendEmptyResponse(HTTP_FORBIDDEN, ex);
return sendContent(ex, mailConfig.map());
}
private boolean saveSettings(HttpExchange ex, User user) throws IOException {
if (!user.hasPermission(MANAGE_SMTP)) return sendEmptyResponse(HTTP_FORBIDDEN, ex);
private boolean saveSettings(HttpExchange ex, Session session) throws IOException {
if (!session.user().hasPermission(MANAGE_SMTP)) return sendEmptyResponse(HTTP_FORBIDDEN, ex);
var data = json(ex);
if (data.has(SMTP_HOST)) mailConfig.smtpHost(data.getString(SMTP_HOST));
if (data.has(SMTP_PORT)) mailConfig.smtpPort(data.getInt(SMTP_PORT));

117
de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java

@ -51,6 +51,8 @@ public class UserController extends Controller { @@ -51,6 +51,8 @@ public class UserController extends Controller {
// post-login paths
var session = optSession.get();
sessions.extend(session);
switch (path) {
case "/delete":
return deleteUser(ex, session);
@ -84,6 +86,8 @@ public class UserController extends Controller { @@ -84,6 +86,8 @@ public class UserController extends Controller {
// post-login paths
var session = optSession.get();
sessions.extend(session);
switch (path) {
case "/logout":
return logout(ex, session);
@ -106,6 +110,8 @@ public class UserController extends Controller { @@ -106,6 +110,8 @@ public class UserController extends Controller {
// post-login paths
var session = optSession.get();
sessions.extend(session);
switch (path) {
case "/":
return sendUserAndCookie(ex, session);
@ -121,28 +127,26 @@ public class UserController extends Controller { @@ -121,28 +127,26 @@ public class UserController extends Controller {
return notFound(ex);
}
private boolean resetPassword(HttpExchange ex) throws IOException {
var data = json(ex);
if (!data.has(TOKEN)) return sendContent(ex, HTTP_UNAUTHORIZED, "token missing");
var newpass = data.getJSONArray("newpass");
var newPass1 = newpass.getString(0);
if (!newPass1.equals(newpass.getString(1))) {
return badRequest(ex, "password mismatch");
private boolean generateResetLink(HttpExchange ex) throws IOException {
var idOrEmail = queryParam(ex).get("user");
var url = url(ex) //
.replace("/api/user/", "/web/")
.split("\\?")[0] +
".html";
Set<User> matchingUsers = users.find(idOrEmail);
if (!matchingUsers.isEmpty()) {
resourceLoader //
.loadFile(language(ex), "reset_password.template.txt")
.map(ResourceLoader.Resource::content)
.map(bytes -> new String(bytes, UTF_8))
.ifPresent(template -> { //
matchingUsers.forEach(user -> sendResetLink(user, template, url));
});
}
if (!strong(newPass1)) return sendContent(ex, HTTP_BAD_REQUEST, "weak password");
var token = data.getString(TOKEN);
var optUser = users.consumeToken(token);
if (optUser.isEmpty()) return sendContent(ex, HTTP_UNAUTHORIZED, "invalid token");
var user = optUser.get();
users.updatePassword(user, newPass1);
var session = sessions.createSession(user);
new SessionToken(session.id()).addTo(ex);
return sendRedirect(ex, "/");
return sendEmptyResponse(HTTP_OK, ex);
}
private boolean strong(String pass) {
return pass.length() > 10; // TODO
}
private boolean list(HttpExchange ex, Session session) throws IOException {
var user = session.user();
@ -169,25 +173,31 @@ public class UserController extends Controller { @@ -169,25 +173,31 @@ public class UserController extends Controller {
return sendEmptyResponse(HTTP_OK, ex);
}
private boolean generateResetLink(HttpExchange ex) throws IOException {
var idOrEmail = queryParam(ex).get("user");
var url = url(ex) //
.replace("/api/user/", "/web/")
.split("\\?")[0] +
".html";
Set<User> matchingUsers = users.find(idOrEmail);
if (!matchingUsers.isEmpty()) {
resourceLoader //
.loadFile(language(ex), "reset_password.template.txt")
.map(ResourceLoader.Resource::content)
.map(bytes -> new String(bytes, UTF_8))
.ifPresent(template -> { //
matchingUsers.forEach(user -> sendResetLink(user, template, url));
});
private boolean resetPassword(HttpExchange ex) throws IOException {
var data = json(ex);
if (!data.has(TOKEN)) return sendContent(ex, HTTP_UNAUTHORIZED, "token missing");
var passwords = data.getJSONArray("newpass");
var newPass = passwords.getString(0);
if (!newPass.equals(passwords.getString(1))) {
return badRequest(ex, "password mismatch");
}
return sendEmptyResponse(HTTP_OK, ex);
try {
strong(newPass);
} catch (RuntimeException e) {
return sendContent(ex, HTTP_BAD_REQUEST, e.getMessage());
}
var token = data.getString(TOKEN);
var optUser = users.consumeToken(token);
if (optUser.isEmpty()) return sendContent(ex, HTTP_UNAUTHORIZED, "invalid token");
var user = optUser.get();
users.updatePassword(user, newPass);
var session = sessions.createSession(user);
new SessionToken(session.id()).addTo(ex);
return sendRedirect(ex, "/");
}
private void sendResetLink(User user, String template, String url) {
LOG.log(WARNING, "Sending password link to {0}", user.email());
var token = users.accessToken(user);
@ -228,6 +238,30 @@ public class UserController extends Controller { @@ -228,6 +238,30 @@ public class UserController extends Controller {
return sendContent(ex, session.user().map(false));
}
private void strong(String pass) {
var digits = false;
var special = false;
var alpha = false;
for (int i = 0; i < pass.length(); i++) {
char c = pass.charAt(i);
if (Character.isDigit(c)) {
digits = true;
} else if (Character.isAlphabetic(c)) {
alpha = true;
} else
special = true;
}
if (pass.length() < 16) {
if (!alpha) throw new RuntimeException("Passwords shorter than 16 characters must contain alphabetic characters!");
if (!digits && !special) throw new RuntimeException("Passwords shorter than 16 characters must contain at least one digit or special character!");
if (pass.length() < 10) {
if (!digits || !special) throw new RuntimeException("Passwords shorter than 10 characters must contain digits as well as alphabetic and special characters!");
if (pass.length() < 6) throw new RuntimeException("Password must have at least 6 characters!");
}
}
}
private boolean updatePassword(HttpExchange ex, Session session) throws IOException {
var user = session.user();
var json = json(ex);
@ -238,12 +272,17 @@ public class UserController extends Controller { @@ -238,12 +272,17 @@ public class UserController extends Controller {
var oldPass = json.getString("oldpass");
if (!users.passwordMatches(oldPass, user.hashedPassword())) return badRequest(ex, "wrong password");
var newpass = json.getJSONArray("newpass");
var newPass1 = newpass.getString(0);
if (!newPass1.equals(newpass.getString(1))) {
var passwords = json.getJSONArray("newpass");
var newPass = passwords.getString(0);
if (!newPass.equals(passwords.getString(1))) {
return badRequest(ex, "password mismatch");
}
users.updatePassword(user, newPass1);
try {
strong(newPass);
} catch (RuntimeException e) {
return sendContent(ex, HTTP_BAD_REQUEST, e.getMessage());
}
users.updatePassword(user, newPass);
return sendContent(ex, user.map(false));
}

6
de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java

@ -239,8 +239,10 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe @@ -239,8 +239,10 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
}
@Override
public Session extend(String sessionId) {
return null;
public Session extend(Session session) {
var user = session.user();
var endOfSession = Instant.now().plus(user.sessionDuration());
return save(new Session(user, endOfSession, session.id()));
}
private JSONObject sessions() {

4
de.srsoftware.oidc.web/src/main/resources/en/todo.html

@ -12,12 +12,8 @@ @@ -12,12 +12,8 @@
<div id="content">
<h1>to do…</h1>
<ul>
<li>at_hash in ID Token</li>
<li>Session bei Aktivität verlängern</li>
<li>implement token refresh</li>
<li>handle https correctly in PathHandler.hostname</li>
<li>Verschlüsselung im config-File</li>
<li>bessere Implementierung für UserController.strong(pass), anwendung überall da wo passworte geändert werden können</li>
</ul>
</div>
</body>

Loading…
Cancel
Save