/*
|
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 <https://www.gnu.org/licenses/>.
|
*/
|
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;
|
}
|
|
}
|