/* neon-auth - Authentication Extensions to Neon Copyright (C) 2024 Ulrich Hilger This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ package de.uhilger.neon.auth; import com.google.gson.Gson; import com.sun.net.httpserver.HttpContext; import com.sun.net.httpserver.HttpExchange; import de.uhilger.neon.HttpHelper; import java.io.IOException; import java.util.Date; import java.util.Map; /** * Objekte der Klasse BearerLoginService erlauben eine Benutzeranmeldung * mit Hilfe eines BearerAuthenticators, der im HttpContext des * HttpExchange erwartet wird * * @author Ulrich Hilger */ public class BearerLoginService extends BearerService { private final Directory directory; private long lastLoginTime = 0; public BearerLoginService(Directory directory) { this.directory = directory; } public void login(HttpExchange exchange) throws IOException { User user = getUser(exchange); LoginResponse response = login(exchange, user.getName(), user.getPassword()); handleLoginResponse(exchange, response); } /** * Anmelden * * @param e das Objekt mit Informationen zu HTTP-Anfrage und -Antwort * @param userId die Kennung des Benutzers * @param password das Kennwort des Benutzers * @return Token oder null, wenn die Anmeldung misslang */ private LoginResponse login(HttpExchange e, String userId, String password) { HttpContext context = e.getHttpContext(); Map attr = context.getAttributes(); if(canLogin(attr)) { if (directory.isValid(userId, password)) { LoginResponse r = new LoginResponse(); long expireSeconds = Long.parseLong((String) attr.getOrDefault("expireSeconds", "7200")); Object o = context.getAuthenticator(); if(o instanceof BearerAuthenticator) { String token = ((BearerAuthenticator) o).createToken(userId, expireSeconds); r.setToken(token); r.setRefreshToken(((BearerAuthenticator) o).createToken(userId, Long.parseLong((String) attr.getOrDefault("refreshExpireSeconds", "86400")))); r.setExpiresIn(expireSeconds); return r; } else { return null; } } else { return null; } } else { return null; } } /** * Bei der Deklaration eines BearerAuthenticator in der Serverbeschreibung wird ein * Attribut z.B. "loginTL": 10000 erwartet. Hier wird geprueft, ob die dort angegebene * Zahl von Millisekunden verstrichen ist, seit dieser LoginService das letzte Mal * aufgerufen wurde. Auf diese Weise wird ueber alle Logins, die ueber diesen Service * ausgefuehrt werden, eine Brute Force Attacke gebremst. Das heisst aber auch, dass * jeder Login fruehestens nach der Anzahl von Millisekunden seit dem letzten Login * erfolgen kann. * * @param attr die Attribute, in denen das Attribut 'loginTL' erwartet wird * @return true, wenn die konfigurierte Wartezeit verstrichen ist, false, wenn nicht */ private boolean canLogin(Map attr) { boolean doLogin = false; long loginTl = Long.parseLong((String) attr.getOrDefault("loginTL", "10000")); if(loginTl > 0 ) { Date now = new Date(); long nowTimeMillis = now.getTime(); long diff = nowTimeMillis - lastLoginTime; if(loginTl < diff) { lastLoginTime = nowTimeMillis; doLogin = true; } } else { doLogin = true; } return doLogin; } private User getUser(HttpExchange exchange) throws IOException { /* Wenn ein JSON-Inhalt im Body uebermittelt wird, steht dort evtl. etwas wie {"name": "fred", "password": "secret"} das kann wie folgt gelesen werden */ String body = new HttpHelper().bodyLesen(exchange); Gson gson = new Gson(); User user = gson.fromJson(body, User.class); return user; } }