33 changed files with 1232 additions and 32 deletions
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
plugins { |
||||
id("java") |
||||
} |
||||
|
||||
group = "de.srsoftware" |
||||
version = "unspecified" |
||||
|
||||
repositories { |
||||
mavenCentral() |
||||
} |
||||
|
||||
dependencies { |
||||
testImplementation(platform("org.junit:junit-bom:5.10.0")) |
||||
testImplementation("org.junit.jupiter:junit-jupiter") |
||||
implementation("org.xerial:sqlite-jdbc:3.49.0.0") |
||||
} |
||||
|
||||
tasks.test { |
||||
useJUnitPlatform() |
||||
} |
||||
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.core; |
||||
|
||||
import java.util.HashMap; |
||||
|
||||
public class AddableMap extends HashMap<String,Object> { |
||||
public AddableMap plus(Object...param){ |
||||
if (param.length % 2 == 1) throw new RuntimeException("Expectirg number of parameters to be even"); |
||||
for (int i=0; i<param.length; i+=2) put(param[i].toString(),param[i+1]); |
||||
return this; |
||||
} |
||||
} |
||||
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.core; |
||||
|
||||
import java.io.File; |
||||
import java.sql.Connection; |
||||
import java.sql.SQLException; |
||||
import java.util.HashMap; |
||||
import org.sqlite.SQLiteDataSource; |
||||
|
||||
public class ConnectionProvider extends HashMap<File, Connection> { |
||||
public Connection get(Object o) { |
||||
if (o instanceof String filename) o = new File(filename); |
||||
if (o instanceof File dbFile) try { |
||||
var conn = super.get(dbFile); |
||||
if (conn == null) put(dbFile, conn = open(dbFile)); |
||||
return conn; |
||||
} catch (SQLException sqle) { |
||||
throw new RuntimeException(sqle); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
private Connection open(File dbFile) throws SQLException { |
||||
SQLiteDataSource dataSource = new SQLiteDataSource(); |
||||
dataSource.setUrl("jdbc:sqlite:%s".formatted(dbFile)); |
||||
return dataSource.getConnection(); |
||||
} |
||||
} |
||||
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.core; |
||||
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8; |
||||
|
||||
public class Constants { |
||||
|
||||
public static final String ADDRESS = "address"; |
||||
public static final String ATTACHMENTS = "attachments"; |
||||
public static final String BODY = "body"; |
||||
public static final String DATA = "data"; |
||||
public static final String DATE = "date"; |
||||
public static final String DEFAULT_LANGUAGE = "en"; |
||||
public static final String DEFAULT_THEME = "winter"; |
||||
public static final String DESCRIPTION = "description"; |
||||
public static final String DOMAIN = "domain"; |
||||
public static final String EMAIL = "email"; |
||||
|
||||
public static final String ERROR_FAILED_CREATE_TABLE = "Failed to create \"{0}\" table!"; |
||||
public static final String ERROR_INVALID_FIELD = "Expected {0} to be {1}!"; |
||||
public static final String ERROR_MISSING_FIELD = "Json is missing {0} field!"; |
||||
public static final String ERROR_READ_TABLE = "Failed to read {0} from {1} table"; |
||||
|
||||
public static final String EXPIRATION = "expiration"; |
||||
public static final String ID = "id"; |
||||
public static final String KEY = "key"; |
||||
public static final String LANGUAGE = "language"; |
||||
public static final String LOGIN = "login"; |
||||
public static final String MESSAGES = "messages"; |
||||
public static final String NAME = "name"; |
||||
public static final String MIME = "mime"; |
||||
public static final String NUMBER = "number"; |
||||
public static final String OPTIONAL = "optional"; |
||||
public static final String PASSWORD = "password"; |
||||
|
||||
public static final String RECEIVERS = "receivers"; |
||||
public static final String REDIRECT = "redirect"; |
||||
public static final String SENDER = "sender"; |
||||
public static final String SETTINGS = "settings"; |
||||
public static final String STATE = "state"; |
||||
public static final String STATUS_CODE = "code"; |
||||
public static final String STRING = "string"; |
||||
public static final String SUBJECT = "subject"; |
||||
|
||||
public static final String TABLE_SETTINGS = "settings"; |
||||
|
||||
public static final String TEMPLATE = "template"; |
||||
public static final String TEXT = "text"; |
||||
public static final String THEME = "theme"; |
||||
public static final String TITLE = "title"; |
||||
public static final String TOKEN = "token"; |
||||
public static final String UMBRELLA = "Umbrella"; |
||||
public static final String URL = "url"; |
||||
public static final String USER = "user"; |
||||
public static final String USER_ID = "user_id"; |
||||
public static final String USER_LIST = "user_list"; |
||||
public static final String UTF8 = UTF_8.displayName(); |
||||
public static final String VALUE = "value"; |
||||
} |
||||
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.core; |
||||
|
||||
public class Paths { |
||||
private Paths(){}; |
||||
|
||||
public static final String ADD = "add"; |
||||
public static final String CSS = "css"; |
||||
public static final String JSON = "json"; |
||||
public static final String LEGACY = "legacy"; |
||||
public static final String LIST = "list"; |
||||
public static final String LOGOUT = "logout"; |
||||
public static final String SEARCH = "search"; |
||||
public static final String SERVICE = "service"; |
||||
public static final String SETTINGS = "settings"; |
||||
public static final String SUBMIT = "submit"; |
||||
public static final String TOKEN = "token"; |
||||
public static final String VIEW = "view"; |
||||
} |
||||
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.core; |
||||
|
||||
public class ResponseCode { |
||||
public static final int OK = 200; |
||||
public static final int REDIRECT = 302; |
||||
public static final int BAD_REQUEST = 400; |
||||
public static final int UNAUTHORIZED = 401; |
||||
public static final int FORBIDDEN = 403; |
||||
public static final int NOT_FOUND = 404; |
||||
public static final int UNPROCESSABLE = 422; |
||||
public static final int SERVER_ERROR = 500; |
||||
public static final int NOT_IMPLEMENTED = 501; |
||||
} |
||||
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.core; |
||||
|
||||
import static java.text.MessageFormat.format; |
||||
|
||||
|
||||
public class UmbrellaException extends Exception{ |
||||
private final int statusCode; |
||||
|
||||
public UmbrellaException(int statusCode, String message){ |
||||
super(message); |
||||
this.statusCode = statusCode; |
||||
} |
||||
|
||||
public UmbrellaException(int statusCode, String message, Object ... fills){ |
||||
this(statusCode,format(message,fills)); |
||||
} |
||||
|
||||
public UmbrellaException causedBy(Exception e) { |
||||
initCause(e); |
||||
return this; |
||||
} |
||||
|
||||
public int statusCode(){ |
||||
return statusCode; |
||||
} |
||||
} |
||||
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
<script> |
||||
import { t } from '../translations.svelte.js'; |
||||
</script> |
||||
<footer> |
||||
{@html t('footer.message','<a href="https://srsoftware.de">SRSoftware</a>')} |
||||
</footer> |
||||
@ -1,6 +1,11 @@
@@ -1,6 +1,11 @@
|
||||
description = "Umbrella : User" |
||||
|
||||
dependencies{ |
||||
implementation(project(":core")) |
||||
implementation("de.srsoftware:tools.jdbc:1.3.2") |
||||
implementation("de.srsoftware:tools.mime:1.1.2") |
||||
implementation("de.srsoftware:tools.optionals:1.0.0") |
||||
implementation("de.srsoftware:tools.util:2.0.3") |
||||
implementation("org.json:json:20240303") |
||||
implementation("org.xerial:sqlite-jdbc:3.49.0.0") |
||||
} |
||||
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.user;/* © SRSoftware 2025 */ |
||||
|
||||
import static de.srsoftware.tools.Strings.hex; |
||||
import static java.nio.charset.StandardCharsets.UTF_8; |
||||
|
||||
import de.srsoftware.tools.PasswordHasher; |
||||
import java.security.MessageDigest; |
||||
import java.security.NoSuchAlgorithmException; |
||||
|
||||
/** |
||||
* This is a bad hasher for several reasons: |
||||
* First, it uses sha-1, which is regarded outdated nowadays. |
||||
* Second, it does not use salted passwords, which makes it prone to rainbow table attacks |
||||
*/ |
||||
public class BadHasher implements PasswordHasher<String> { |
||||
private static final String SHA1 = "SHA-1"; |
||||
|
||||
private final MessageDigest digest; |
||||
|
||||
/** |
||||
* Create a new instance |
||||
* @throws NoSuchAlgorithmException if SHA256 cannot be instantiated |
||||
*/ |
||||
public BadHasher() throws NoSuchAlgorithmException { |
||||
digest = MessageDigest.getInstance(SHA1); |
||||
} |
||||
|
||||
@Override |
||||
public String hash(String password, String ignored) { |
||||
var bytes = digest.digest(password.getBytes(UTF_8)); |
||||
|
||||
return hex(bytes); |
||||
} |
||||
|
||||
@Override |
||||
public String salt(String hashedPassword) { |
||||
return null; |
||||
} |
||||
} |
||||
@ -0,0 +1,67 @@
@@ -0,0 +1,67 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.user; |
||||
|
||||
import java.time.Duration; |
||||
|
||||
public class Constants { |
||||
private Constants(){} |
||||
|
||||
public static final String AUTH_ENDPOINT = "authorization_endpoint"; |
||||
public static final String AUTHORIZATION_CODE = "authorization_code"; |
||||
|
||||
public static final String CLIENT_ID = "client_id"; |
||||
public static final String CLIENT_SECRET = "client_secret"; |
||||
public static final String CODE = "code"; |
||||
|
||||
|
||||
public static final String DB_VERSION = "user_db_version"; |
||||
public static final String DEFAULT_FIELD = "sub"; |
||||
public static final Duration DEFAULT_SESSION_DURATION = Duration.ofMinutes(20); |
||||
|
||||
public static final String FOREIGN_ID = "foreign_id"; |
||||
public static final String GRANT_TYPE = "grant_type"; |
||||
|
||||
public static final String IDS = "ids"; |
||||
public static final String ID_TOKEN = "id_token"; |
||||
public static final String JWKS_ENDPOINT = "jwks_uri"; |
||||
|
||||
public static final String LAST_LOGOFF = "last_logoff"; |
||||
public static final String LOGIN_SERVICES = "login_services"; |
||||
public static final String MESSAGE_DELIVERY = "message_delivery"; |
||||
public static final String OIDC_CALLBACK = "redirect_uri"; |
||||
public static final String OIDC_LINK = "oidc_link"; |
||||
public static final String OIDC_SCOPE = "openid"; |
||||
|
||||
public static final String PASS = "pass"; |
||||
|
||||
public static final String PATH_CALLBACK = "callback"; |
||||
public static final String PATH_DASH = "dash"; |
||||
public static final String PATH_IMPERSONATE = "impersonate"; |
||||
public static final String PATH_INSTALL = "install"; |
||||
public static final String PATH_JAVASCRIPT = "js"; |
||||
public static final String PATH_LOGIN = "login"; |
||||
public static final String PATH_MENU = "menu"; |
||||
public static final String PATH_NOTIFY = "notify"; |
||||
public static final String PATH_OIDC_BUTTONS = "oidc_buttons"; |
||||
public static final String PATH_OIDC_LOGIN = "oidc_login"; |
||||
public static final String PATH_OPENID_LOGIN = "openid_login"; |
||||
public static final String PATH_SESSION = "session"; |
||||
public static final String PATH_VALIDATE_TOKEN = "validateToken"; |
||||
|
||||
|
||||
public static final String REDIRECT_URI = "redirect_uri"; |
||||
public static final String RESPONSE_TYPE = "response_type"; |
||||
public static final String SCOPE = "scope"; |
||||
public static final String SERVICE = "service"; |
||||
public static final String SERVICE_ID = "service_id"; |
||||
|
||||
public static final String TABLE_LOGIN_SERVICES = "user_login_services"; |
||||
public static final String TABLE_SERVICE_IDS_USERS = "user_service_ids_users"; |
||||
public static final String TABLE_TOKENS = "tokens"; |
||||
public static final String TABLE_TOKEN_USES = "token_uses"; |
||||
public static final String TABLE_USERS = "users"; |
||||
|
||||
public static final String TOKEN_ENDPOINT = "token_endpoint"; |
||||
public static final String USER_INFO_FIELD = "user_info_field"; |
||||
public static final String USERNAME = "username"; |
||||
} |
||||
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.user.api; |
||||
|
||||
import de.srsoftware.umbrella.core.UmbrellaException; |
||||
import de.srsoftware.umbrella.user.model.ForeignLogin; |
||||
import de.srsoftware.umbrella.user.model.LoginService; |
||||
import java.util.List; |
||||
|
||||
public interface LoginServiceDb { |
||||
String delete(String serviceName) throws UmbrellaException; |
||||
long getUserId(String loginService, String foreignUserId) throws UmbrellaException; |
||||
List<ForeignLogin> listAssignments(long userId) throws UmbrellaException; |
||||
List<LoginService> listLoginServices() throws UmbrellaException; |
||||
LoginService loadLoginService(String id) throws UmbrellaException; |
||||
ForeignLogin save(ForeignLogin foreignLogin) throws UmbrellaException; |
||||
LoginService save(LoginService service) throws UmbrellaException; |
||||
ForeignLogin unlink(ForeignLogin of) throws UmbrellaException; |
||||
} |
||||
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.user.api; |
||||
|
||||
import de.srsoftware.umbrella.core.UmbrellaException; |
||||
import de.srsoftware.umbrella.user.model.DbUser; |
||||
import de.srsoftware.umbrella.user.model.Password; |
||||
import de.srsoftware.umbrella.user.model.Session; |
||||
import de.srsoftware.umbrella.user.model.Token; |
||||
import de.srsoftware.umbrella.user.model.UmbrellaUser; |
||||
import java.util.List; |
||||
|
||||
public interface UserDb { |
||||
|
||||
Long delete(Long userId) throws UmbrellaException; |
||||
|
||||
Boolean dropSession(Token token) throws UmbrellaException; |
||||
|
||||
/** |
||||
* Get a session for the provided user. |
||||
* @param user |
||||
* @return |
||||
*/ |
||||
Session getSession(UmbrellaUser user) throws UmbrellaException; |
||||
|
||||
Session extend(Session session) throws UmbrellaException; |
||||
|
||||
List<UmbrellaUser> list(Integer start, Integer limit, Long ... ids) throws UmbrellaException; |
||||
|
||||
Session load(Token token) throws UmbrellaException; |
||||
|
||||
UmbrellaUser load(Long id) throws UmbrellaException; |
||||
|
||||
UmbrellaUser load(Session session) throws UmbrellaException; |
||||
|
||||
UmbrellaUser load(String key, Password password) throws UmbrellaException; |
||||
|
||||
UmbrellaUser save(DbUser user) throws UmbrellaException; |
||||
} |
||||
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.user.model; |
||||
|
||||
|
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
public class DbUser extends UmbrellaUser { |
||||
|
||||
public enum PERMISSION { |
||||
CREATE_USERS, |
||||
DELETE_USERS, |
||||
LIST_USERS, |
||||
IMPERSONATE, |
||||
MANAGE_LOGIN_SERVICES |
||||
} |
||||
|
||||
private final Set<PERMISSION> permissions; |
||||
private final Password hashedPass; |
||||
private final Long lastLogoff; |
||||
|
||||
public DbUser(long id, String name, String email, Password hashedPassword, String theme, String languageCode, Set<PERMISSION> permissions, Long lastLogoff) { |
||||
super(id, name, email, theme, languageCode); |
||||
this.hashedPass = hashedPassword; |
||||
this.permissions = permissions; |
||||
this.lastLogoff = lastLogoff; |
||||
} |
||||
|
||||
public Password hashedPassword(){ |
||||
return hashedPass; |
||||
} |
||||
|
||||
public Long lastLogoff() { |
||||
return lastLogoff; |
||||
} |
||||
|
||||
public boolean may(PERMISSION permission){ |
||||
return permissions.contains(permission); |
||||
} |
||||
|
||||
public Set<PERMISSION> permissions() { |
||||
return permissions; |
||||
} |
||||
|
||||
@Override |
||||
public Map<String, Object> toMap() { |
||||
var map = super.toMap(); |
||||
map.put("permissions",permissions); |
||||
return map; |
||||
} |
||||
} |
||||
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.user.model; |
||||
|
||||
import static de.srsoftware.umbrella.core.Constants.USER_ID; |
||||
import static de.srsoftware.umbrella.user.Constants.FOREIGN_ID; |
||||
import static de.srsoftware.umbrella.user.Constants.SERVICE; |
||||
|
||||
import de.srsoftware.tools.Mappable; |
||||
import java.util.Map; |
||||
|
||||
public record ForeignLogin(String loginService, String foreingId, Long userId) implements Mappable { |
||||
public static ForeignLogin of(String serviceName, String externalId, long userId) { |
||||
return new ForeignLogin(serviceName,externalId,userId); |
||||
} |
||||
|
||||
@Override |
||||
public Map<String, Object> toMap() { |
||||
return Map.of(SERVICE,loginService,FOREIGN_ID,foreingId, USER_ID,userId); |
||||
} |
||||
} |
||||
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.user.model; |
||||
|
||||
|
||||
import static de.srsoftware.tools.Strings.base64; |
||||
import static de.srsoftware.umbrella.core.Constants.NAME; |
||||
import static de.srsoftware.umbrella.core.Constants.URL; |
||||
import static java.nio.charset.StandardCharsets.UTF_8; |
||||
|
||||
import de.srsoftware.tools.Mappable; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
public record LoginService(String name, String url, String clientId, String clientSecret, String userInfoField) implements Mappable { |
||||
|
||||
@Override |
||||
public Map<String,Object> toMap() { |
||||
var map = new HashMap<String,Object>(); |
||||
map.put(NAME,name); |
||||
map.put(URL,url); |
||||
map.put("clientId",clientId); |
||||
map.put("clientSecret",clientSecret); |
||||
map.put("userInfoField",userInfoField); |
||||
return map; |
||||
} |
||||
|
||||
public String basicAuth() { |
||||
return "Basic "+base64((clientId+":"+clientSecret).getBytes(UTF_8)); |
||||
} |
||||
} |
||||
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.user.model; |
||||
|
||||
public class Password implements CharSequence{ |
||||
|
||||
private final String pass; |
||||
|
||||
private Password(String val){ |
||||
this.pass = val; |
||||
} |
||||
|
||||
@Override |
||||
public int length() { |
||||
return pass.length(); |
||||
} |
||||
|
||||
@Override |
||||
public char charAt(int i) { |
||||
return pass.charAt(i); |
||||
} |
||||
|
||||
public static Password of(String val){ |
||||
return new Password(val); |
||||
} |
||||
|
||||
@Override |
||||
public CharSequence subSequence(int start, int end) { |
||||
return pass.subSequence(start,end); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return pass; |
||||
} |
||||
} |
||||
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.user.model; |
||||
|
||||
import de.srsoftware.tools.SessionToken; |
||||
import java.time.Instant; |
||||
|
||||
/* © SRSoftware 2025 */ |
||||
public record Session(UmbrellaUser user, Token token, Instant expiration) { |
||||
public Session extended(Instant newExpiration) { |
||||
return new Session(user,token,newExpiration); |
||||
} |
||||
|
||||
public SessionToken cookie() { |
||||
return new SessionToken(token.toString(),"/",expiration,true); |
||||
} |
||||
} |
||||
@ -0,0 +1,59 @@
@@ -0,0 +1,59 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.user.model; |
||||
|
||||
import static de.srsoftware.umbrella.core.Constants.TOKEN; |
||||
|
||||
import de.srsoftware.tools.SessionToken; |
||||
import de.srsoftware.umbrella.core.AddableMap; |
||||
import java.util.UUID; |
||||
|
||||
public class Token implements CharSequence{ |
||||
|
||||
private final String token; |
||||
|
||||
public Token(){ |
||||
token = UUID.randomUUID().toString(); |
||||
} |
||||
|
||||
private Token(String value){ |
||||
this.token = value; |
||||
} |
||||
|
||||
public String asBearer() { |
||||
return "Bearer "+token; |
||||
} |
||||
|
||||
public AddableMap asMap(){ |
||||
return new AddableMap().plus(TOKEN,token); |
||||
} |
||||
|
||||
@Override |
||||
public char charAt(int i) { |
||||
return token.charAt(i); |
||||
} |
||||
|
||||
public static Token of(String val){ |
||||
return new Token(val); |
||||
} |
||||
|
||||
public static Token of(SessionToken sessionToken) { |
||||
return new Token(sessionToken.sessionId()); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public int length() { |
||||
return token.length(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public CharSequence subSequence(int start, int end) { |
||||
return token.subSequence(start,end); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return token; |
||||
} |
||||
} |
||||
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.user.model; |
||||
|
||||
|
||||
import static de.srsoftware.umbrella.core.Constants.*; |
||||
|
||||
import de.srsoftware.tools.Mappable; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
/* © SRSoftware 2025 */ |
||||
public class UmbrellaUser extends User implements Mappable { |
||||
|
||||
private final long id; |
||||
private final String theme, lang; |
||||
|
||||
public UmbrellaUser(long id, String name, String email, String theme, String languageCode) { |
||||
super(name,email); |
||||
this.id = id; |
||||
this.theme = theme; |
||||
this.lang = languageCode; |
||||
} |
||||
|
||||
|
||||
public long id(){ |
||||
return id; |
||||
} |
||||
|
||||
public String language(){ |
||||
return lang; |
||||
} |
||||
|
||||
|
||||
public String theme(){ |
||||
return theme; |
||||
} |
||||
|
||||
@Override |
||||
public Map<String,Object> toMap() { |
||||
var map = new HashMap<String,Object>(); |
||||
map.put(ID,id); |
||||
map.put(LOGIN, name()); // this is still used by old umbrella modules
|
||||
map.put(NAME, name()); |
||||
map.put(EMAIL,email()); |
||||
map.put(THEME,theme); |
||||
map.put(LANGUAGE,lang); |
||||
return map; |
||||
} |
||||
} |
||||
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.user.model; |
||||
|
||||
import static de.srsoftware.tools.Optionals.nullable; |
||||
import static java.text.MessageFormat.format; |
||||
|
||||
import java.util.Objects; |
||||
|
||||
public class User { |
||||
|
||||
private final String email, name; |
||||
|
||||
public User(String name, String email) { |
||||
this.name = name; |
||||
this.email = email; |
||||
} |
||||
|
||||
public String email(){ |
||||
return email; |
||||
} |
||||
public String name(){ |
||||
return name; |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (!(o instanceof User user)) return false; |
||||
return Objects.equals(email, user.email) && Objects.equals(name, user.name); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return Objects.hash(email, name); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return format("{1}({0})", nullable(name()).orElse(email()),getClass().getSimpleName()); |
||||
} |
||||
} |
||||
@ -0,0 +1,500 @@
@@ -0,0 +1,500 @@
|
||||
/* © SRSoftware 2025 */ |
||||
package de.srsoftware.umbrella.user.sqlite; |
||||
|
||||
import static de.srsoftware.tools.jdbc.Condition.*; |
||||
import static de.srsoftware.tools.jdbc.Query.*; |
||||
import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; |
||||
import static de.srsoftware.umbrella.core.Constants.*; |
||||
import static de.srsoftware.umbrella.user.Constants.*; |
||||
import static de.srsoftware.umbrella.user.model.DbUser.PERMISSION.*; |
||||
import static java.lang.System.Logger.Level.*; |
||||
import static java.text.MessageFormat.format; |
||||
|
||||
import de.srsoftware.tools.jdbc.Query; |
||||
import de.srsoftware.umbrella.core.UmbrellaException; |
||||
import de.srsoftware.umbrella.user.api.LoginServiceDb; |
||||
import de.srsoftware.umbrella.user.api.UserDb; |
||||
import de.srsoftware.umbrella.user.model.*; |
||||
import de.srsoftware.umbrella.user.model.Session; |
||||
import de.srsoftware.umbrella.user.model.Token; |
||||
import de.srsoftware.umbrella.user.model.UmbrellaUser; |
||||
import java.sql.Connection; |
||||
import java.sql.ResultSet; |
||||
import java.sql.SQLException; |
||||
import java.time.Instant; |
||||
import java.time.LocalDateTime; |
||||
import java.time.ZoneOffset; |
||||
import java.util.*; |
||||
|
||||
public class SqliteDB implements LoginServiceDb, UserDb { |
||||
private static final System.Logger LOG = System.getLogger(SqliteDB.class.getSimpleName()); |
||||
|
||||
private static final int INITIAL_DB_VERSION = 4; |
||||
|
||||
private HashMap<String,Session> sessions = new HashMap<>(); |
||||
private final Connection db; |
||||
|
||||
public SqliteDB(Connection conn){ |
||||
db = conn; |
||||
init(); |
||||
} |
||||
|
||||
|
||||
private void createLoginServiceTables() { |
||||
var createTable = """ |
||||
CREATE TABLE IF NOT EXISTS {0} ( |
||||
{1} VARCHAR(255), |
||||
{2} TEXT, |
||||
{3} VARCHAR(255), |
||||
{4} VARCHAR(255), |
||||
{5} VARCHAR(255), |
||||
PRIMARY KEY ({1}) |
||||
)"""; |
||||
try { |
||||
var stmt = db.prepareStatement(format(createTable,TABLE_LOGIN_SERVICES, NAME, URL,CLIENT_ID,CLIENT_SECRET, USER_INFO_FIELD)); |
||||
stmt.execute(); |
||||
stmt.close(); |
||||
} catch (SQLException e) { |
||||
LOG.log(ERROR,ERROR_FAILED_CREATE_TABLE,TABLE_LOGIN_SERVICES,e); |
||||
throw new RuntimeException(e); |
||||
} |
||||
|
||||
createTable = """ |
||||
CREATE TABLE IF NOT EXISTS {0} ( |
||||
{1} VARCHAR(255) NOT NULL PRIMARY KEY, |
||||
{2} INT NOT NULL |
||||
)"""; |
||||
try { |
||||
var stmt = db.prepareStatement(format(createTable,TABLE_SERVICE_IDS_USERS, SERVICE_ID, USER_ID)); |
||||
stmt.execute(); |
||||
stmt.close(); |
||||
} catch (SQLException e) { |
||||
LOG.log(ERROR,ERROR_FAILED_CREATE_TABLE,TABLE_SERVICE_IDS_USERS,e); |
||||
throw new RuntimeException(e); |
||||
} |
||||
|
||||
createTable = """ |
||||
CREATE TABLE IF NOT EXISTS {0} ( |
||||
{1} VARCHAR(255) NOT NULL, |
||||
{2} TEXT NOT NULL |
||||
)"""; |
||||
try { |
||||
var stmt = db.prepareStatement(format(createTable,TABLE_TOKEN_USES, TOKEN, DOMAIN)); |
||||
stmt.execute(); |
||||
stmt.close(); |
||||
} catch (SQLException e) { |
||||
LOG.log(ERROR,ERROR_FAILED_CREATE_TABLE,TABLE_TOKEN_USES,e); |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
private int createSettingsTable() { |
||||
var createTable = """ |
||||
CREATE TABLE IF NOT EXISTS {0} ( {1} VARCHAR(255) PRIMARY KEY, {2} VARCHAR(255) NOT NULL); |
||||
"""; |
||||
try { |
||||
var stmt = db.prepareStatement(format(createTable,TABLE_SETTINGS, KEY, VALUE)); |
||||
stmt.execute(); |
||||
stmt.close(); |
||||
} catch (SQLException e) { |
||||
LOG.log(ERROR,ERROR_FAILED_CREATE_TABLE,TABLE_SETTINGS,e); |
||||
throw new RuntimeException(e); |
||||
} |
||||
|
||||
Integer version = null; |
||||
try { |
||||
var rs = Query.select(VALUE).from(TABLE_SETTINGS).where(KEY, equal(DB_VERSION)).exec(db); |
||||
if (rs.next()) version = rs.getInt(VALUE); |
||||
rs.close(); |
||||
if (version == null) { |
||||
version = INITIAL_DB_VERSION; |
||||
insertInto(TABLE_SETTINGS, KEY, VALUE).values(DB_VERSION,version).execute(db).close(); |
||||
} |
||||
|
||||
return version; |
||||
} catch (SQLException e) { |
||||
LOG.log(ERROR,ERROR_READ_TABLE,DB_VERSION,TABLE_SETTINGS,e); |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
private int createTables() { |
||||
createUserTables(); |
||||
createLoginServiceTables(); |
||||
return createSettingsTable(); |
||||
} |
||||
|
||||
private void createUserTables() { |
||||
var createTable = """ |
||||
CREATE TABLE IF NOT EXISTS {0} ( |
||||
`{1}` INTEGER, |
||||
`{2}` VARCHAR(255) NOT NULL, |
||||
`{3}` VARCHAR(255) NOT NULL, |
||||
`{4}` VARCHAR(50), |
||||
`{5}` VARCHAR(255), |
||||
`{6}` VARCHAR(100) DEFAULT "DELIVER INSTANTLY", |
||||
`{7}` INT DEFAULT NULL, |
||||
`{8}` TEXT, |
||||
PRIMARY KEY({1}) |
||||
)"""; |
||||
try { |
||||
var stmt = db.prepareStatement(format(createTable,TABLE_USERS, ID, LOGIN, PASSWORD, THEME, EMAIL, MESSAGE_DELIVERY, LAST_LOGOFF, SETTINGS)); |
||||
stmt.execute(); |
||||
stmt.close(); |
||||
} catch (SQLException e) { |
||||
LOG.log(ERROR,ERROR_FAILED_CREATE_TABLE,TABLE_USERS,e); |
||||
throw new RuntimeException(e); |
||||
} |
||||
|
||||
createTable = """ |
||||
CREATE TABLE IF NOT EXISTS {0} ( |
||||
{1} INT NOT NULL PRIMARY KEY, |
||||
{2} VARCHAR(255), |
||||
{3} INTEGER NOT NULL |
||||
)"""; |
||||
try { |
||||
var stmt = db.prepareStatement(format(createTable,TABLE_TOKENS, USER_ID, TOKEN, EXPIRATION)); |
||||
stmt.execute(); |
||||
stmt.close(); |
||||
} catch (SQLException e) { |
||||
LOG.log(ERROR,ERROR_FAILED_CREATE_TABLE,TABLE_USERS,e); |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Long delete(Long userId) throws UmbrellaException { |
||||
try { |
||||
Query.delete().from(TABLE_USERS).where(ID,equal(userId)).execute(db); |
||||
} catch (SQLException e) { |
||||
LOG.log(WARNING,"Failed to delete user with id = {0}!",userId,e); |
||||
throw new UmbrellaException(500,"Failed to delete user with id = {0}!",userId).causedBy(e); |
||||
} |
||||
return userId; |
||||
} |
||||
|
||||
@Override |
||||
public String delete(String serviceName) throws UmbrellaException { |
||||
try { |
||||
Query.delete().from(TABLE_LOGIN_SERVICES).where(NAME,equal(serviceName)).execute(db); |
||||
Query.delete().from(TABLE_SERVICE_IDS_USERS).where(SERVICE_ID,like(serviceName+":%")).execute(db); |
||||
} catch (SQLException e) { |
||||
LOG.log(WARNING,"Failed to delete login service {0}!",serviceName,e); |
||||
throw new UmbrellaException(500,"Failed to delete login service {0}!",serviceName).causedBy(e); |
||||
} |
||||
return serviceName; |
||||
} |
||||
|
||||
private void dropExpiredSessions() { |
||||
LOG.log(DEBUG,"Dropping expired sessions."); |
||||
try { |
||||
var tokens = new HashSet<String>(); |
||||
var rs = select(TOKEN).from(TABLE_TOKENS).where(EXPIRATION,lessThan(now())).exec(db); |
||||
while (rs.next()) tokens.add(rs.getString(TOKEN)); |
||||
rs.close(); |
||||
Query.delete().from(TABLE_TOKEN_USES).where(TOKEN,in(tokens.toArray())).execute(db); |
||||
Query.delete().from(TABLE_TOKENS).where(TOKEN,in(tokens.toArray())).execute(db); |
||||
} catch (SQLException sqle){ |
||||
LOG.log(WARNING,"Failed to drop expired sessions!",sqle); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Boolean dropSession(Token token) throws UmbrellaException { |
||||
LOG.log(DEBUG,"Dropping session associated with {0}.",token); |
||||
try { |
||||
return Query.delete().from(TABLE_TOKENS).where(TOKEN, equal(token)).execute(db); |
||||
} catch (SQLException e){ |
||||
throw new UmbrellaException(500,"Failed to drop session token").causedBy(e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Session extend(Session session) throws UmbrellaException { |
||||
try { |
||||
Instant newExpiration = then(); |
||||
update(TABLE_TOKENS).set(EXPIRATION).where(TOKEN, equal(session.token())).prepare(db).apply(newExpiration.getEpochSecond()); |
||||
LOG.log(DEBUG,"Extended session of user {0} until {1}",session.user().name(),then()); |
||||
return session.extended(newExpiration); |
||||
} catch (SQLException e) { |
||||
LOG.log(WARNING,"Failed to extend session {0}",session.token()); |
||||
throw new UmbrellaException(500,"Failed to extend session {0}",session.token()).causedBy(e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Session getSession(UmbrellaUser user) throws UmbrellaException { |
||||
if (user == null) throw new UmbrellaException(400,"User must not be null"); |
||||
dropExpiredSessions(); |
||||
LOG.log(DEBUG,"Trying to get session for \"{0}\"",user.name()); |
||||
Session session = null; |
||||
try { |
||||
var rs = select(ALL).from(TABLE_TOKENS).where(USER_ID,equal(user.id())).exec(db); |
||||
if (rs.next()) session = new Session(user,Token.of(rs.getString(TOKEN)),Instant.ofEpochSecond(rs.getLong(EXPIRATION))); |
||||
rs.close(); |
||||
if (session != null) return session; |
||||
session = new Session(user,new Token(),then()); |
||||
insertInto(TABLE_TOKENS, USER_ID, TOKEN, EXPIRATION).values(session.user().id(),session.token(),session.expiration().getEpochSecond()).execute(db); |
||||
return session; |
||||
} catch (SQLException e) { |
||||
LOG.log(WARNING,"Failed to request session for \"{0}\" from database",user.name()); |
||||
throw new UmbrellaException(500,"Failed to request session for \"{0}\" from database",user.name()).causedBy(e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public long getUserId(String loginService, String foreignUserId) throws UmbrellaException { |
||||
Long userId = null; |
||||
try { |
||||
var rs = select(USER_ID).from(TABLE_SERVICE_IDS_USERS).where(SERVICE_ID,equal(loginService+":"+foreignUserId)).exec(db); |
||||
if (rs.next()) userId = rs.getLong(USER_ID); |
||||
rs.close(); |
||||
} catch (SQLException sqle){ |
||||
LOG.log(WARNING,"Failed to load user_id for '{0}' @ '{1}'!",foreignUserId,loginService); |
||||
} |
||||
if (userId == null) throw new UmbrellaException(500,"Failed to load user_id for \"{0}\" @ \"{1}\"!",foreignUserId,loginService); |
||||
return userId; |
||||
} |
||||
|
||||
private void init(){ |
||||
var version = createTables(); |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
@Override |
||||
public List<UmbrellaUser> list(Integer start, Integer limit, Long ... ids) throws UmbrellaException { |
||||
var list = new ArrayList<UmbrellaUser>(); |
||||
try { |
||||
var query = select(ALL).from(TABLE_USERS); |
||||
if (start != null && start > 0) query.skip(start); |
||||
if (limit != null && limit > 0) query.limit(limit); |
||||
if (ids != null && ids.length>0) query.where(ID,in((Object[]) ids)); |
||||
var rs = query.exec(db); |
||||
while (rs.next()) list.add(toUser(rs)); |
||||
rs.close(); |
||||
} catch (SQLException e) { |
||||
LOG.log(WARNING,"Failed to load user list from database!",e); |
||||
throw new UmbrellaException(500,"Failed to load user list from database!").causedBy(e); |
||||
} |
||||
return list; |
||||
} |
||||
|
||||
@Override |
||||
public List<ForeignLogin> listAssignments(long userId) throws UmbrellaException { |
||||
try { |
||||
var list = new ArrayList<ForeignLogin>(); |
||||
var rs = select(ALL).from(TABLE_SERVICE_IDS_USERS).where(USER_ID,equal(userId)).exec(db); |
||||
while (rs.next()) list.add(toForeignLogin(rs)); |
||||
rs.close(); |
||||
return list; |
||||
} catch (SQLException e) { |
||||
LOG.log(WARNING,"Failed to fetch {0} for user {1}",TABLE_SERVICE_IDS_USERS,userId,e); |
||||
throw new UmbrellaException(500,"Failed to fetch {0} for user {1}",TABLE_SERVICE_IDS_USERS,userId).causedBy(e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public List<LoginService> listLoginServices() throws UmbrellaException { |
||||
var list = new ArrayList<LoginService>(); |
||||
try { |
||||
var rs = select(ALL).from(TABLE_LOGIN_SERVICES).exec(db); |
||||
while (rs.next()) list.add(toLoginService(rs)); |
||||
rs.close(); |
||||
} catch (SQLException e){ |
||||
LOG.log(WARNING,"Failed to load login service list from database!",e); |
||||
throw new UmbrellaException(500,"Failed to load login service list from database!"); |
||||
} |
||||
return list; |
||||
} |
||||
|
||||
@Override |
||||
public LoginService loadLoginService(String name) throws UmbrellaException { |
||||
LoginService loginService = null; |
||||
try { |
||||
var rs = select(ALL).from(TABLE_LOGIN_SERVICES).where(NAME,equal(name)).exec(db); |
||||
if (rs.next()) loginService = toLoginService(rs); |
||||
rs.close(); |
||||
} catch (SQLException e) { |
||||
LOG.log(WARNING,"Failed to load login service \"{0}\"!",name); |
||||
} |
||||
if (loginService == null) throw new UmbrellaException(500,"Failed to load login service \"{0}\"!",name); |
||||
return loginService; |
||||
} |
||||
|
||||
@Override |
||||
public UmbrellaUser load(Long id) throws UmbrellaException { |
||||
if (id == null) throw new UmbrellaException(400,"Id must not be null!"); |
||||
UmbrellaUser user = null; |
||||
try { |
||||
var rs = select(ALL).from(TABLE_USERS).where(ID,equal(id)).exec(db); |
||||
if (rs.next()) user = toUser(rs); |
||||
rs.close(); |
||||
} catch (SQLException e) { |
||||
LOG.log(WARNING,"Failed to load user \"{0}\"!",id); |
||||
} |
||||
if (user == null) throw new UmbrellaException(500,"Failed to load user \"{0}\"!",id); |
||||
return user; |
||||
} |
||||
|
||||
@Override |
||||
public UmbrellaUser load(Session session) throws UmbrellaException { |
||||
if (session == null) throw new UmbrellaException(500,"Session must not be null!"); |
||||
UmbrellaUser user = null; |
||||
try { |
||||
var rs = select(ALL).from(TABLE_USERS).where(ID,equal(session.user().id())).exec(db); |
||||
if (rs.next()) user = toUser(rs); |
||||
rs.close(); |
||||
} catch (SQLException e) { |
||||
LOG.log(WARNING,"Failed to load user for session {0}!",session.token()); |
||||
} |
||||
if (user == null) throw new UmbrellaException(500,"Failed to load user for session {0}!",session.token()); |
||||
return user; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public UmbrellaUser load(String key, Password password) throws UmbrellaException { |
||||
UmbrellaUser user = null; |
||||
try { |
||||
var rs = select(ALL).from(TABLE_USERS).where(PASS,equal(password)).exec(db); |
||||
while (rs.next()){ |
||||
var email = rs.getString(EMAIL); |
||||
if (key.equalsIgnoreCase(email)) { |
||||
user = toUser(rs); |
||||
break; |
||||
} |
||||
var login = rs.getString(LOGIN); |
||||
if (key.equals(login)) { |
||||
user = toUser(rs); |
||||
break; |
||||
} |
||||
} |
||||
rs.close(); |
||||
} catch (SQLException e) { |
||||
LOG.log(WARNING,"Failed to load user \"{0}\"!",key,e); |
||||
|
||||
} |
||||
if (user == null) throw new UmbrellaException(401,"Failed to load user \"{0}\"!",key); |
||||
return user; |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public Session load(Token token) throws UmbrellaException { |
||||
dropExpiredSessions(); |
||||
Session session = null; |
||||
try { |
||||
var rs = select(ALL).from(TABLE_TOKENS).leftJoin(USER_ID,TABLE_USERS,ID).where(TOKEN,equal(token)).exec(db); |
||||
if (rs.next()) session = new Session(toUser(rs),token,Instant.ofEpochSecond(rs.getLong(EXPIRATION))); |
||||
rs.close(); |
||||
} catch (SQLException e) { |
||||
LOG.log(WARNING,"Failed to request session ({0}) from database",token); |
||||
throw new UmbrellaException(500,"Failed to request session ({0}) from database",token); |
||||
} |
||||
if (session == null) throw new UmbrellaException(500,"Failed to request session ({0}) from database",token); |
||||
return session; |
||||
} |
||||
|
||||
public long now() { |
||||
return LocalDateTime.now().toEpochSecond(ZoneOffset.UTC); |
||||
} |
||||
|
||||
@Override |
||||
public ForeignLogin save(ForeignLogin assignment) throws UmbrellaException { |
||||
try { |
||||
insertInto(TABLE_SERVICE_IDS_USERS, SERVICE_ID, USER_ID).values(assignment.loginService()+":"+assignment.foreingId(),assignment.userId()).execute(db).close(); |
||||
} catch (SQLException e) { |
||||
LOG.log(WARNING,"Failed to insert assignment into table {0}!",TABLE_SERVICE_IDS_USERS,e); |
||||
throw new UmbrellaException(500,"Failed to insert assignment into table {0}!",TABLE_SERVICE_IDS_USERS).causedBy(e); |
||||
} |
||||
return assignment; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public LoginService save(LoginService service) throws UmbrellaException { |
||||
try { |
||||
replaceInto(TABLE_LOGIN_SERVICES, NAME, URL,CLIENT_ID,CLIENT_SECRET) |
||||
.values(service.name(),service.url(),service.clientId(),service.clientSecret()) |
||||
.execute(db) |
||||
.close(); |
||||
} catch (SQLException e){ |
||||
LOG.log(WARNING,"Failed to store login service data for {0}!",service.name(),e); |
||||
throw new UmbrellaException(500,"Failed to store login service for {0}!",service.name()).causedBy(e); |
||||
|
||||
} |
||||
return service; |
||||
} |
||||
|
||||
@Override |
||||
public UmbrellaUser save(DbUser user) throws UmbrellaException { |
||||
try { |
||||
Long id = user.id(); |
||||
if (id<1) id = null; |
||||
replaceInto(TABLE_USERS, ID, LOGIN, PASSWORD, THEME, EMAIL, LAST_LOGOFF) |
||||
.values(id,user.name(),user.hashedPassword(),user.theme(),user.email(),user.lastLogoff()) |
||||
.execute(db) |
||||
.close(); |
||||
} catch (SQLException e) { |
||||
LOG.log(WARNING,"Failed to store user data for {0}!",user.name(),e); |
||||
throw new UmbrellaException(500,"Failed to store user data for {0}!",user.name()).causedBy(e); |
||||
} |
||||
return user; |
||||
} |
||||
|
||||
public Instant then(){ |
||||
return LocalDateTime.now().plus(DEFAULT_SESSION_DURATION).toInstant(ZoneOffset.UTC); |
||||
} |
||||
|
||||
private ForeignLogin toForeignLogin(ResultSet rs) throws SQLException { |
||||
var serviceId = rs.getString(SERVICE_ID); |
||||
var userId = rs.getLong(USER_ID); |
||||
var parts = serviceId.split(":",2); |
||||
var serviceName = parts[0]; |
||||
var externalId = parts[1]; |
||||
return ForeignLogin.of(serviceName,externalId,userId); |
||||
} |
||||
|
||||
private LoginService toLoginService(ResultSet rs) throws SQLException { |
||||
return new LoginService( |
||||
rs.getString(NAME), |
||||
rs.getString(URL), |
||||
rs.getString(CLIENT_ID), |
||||
rs.getString(CLIENT_SECRET), |
||||
rs.getString(USER_INFO_FIELD) |
||||
); |
||||
} |
||||
|
||||
private DbUser toUser(ResultSet rs) throws SQLException { |
||||
long id = rs.getLong(ID); |
||||
Set<DbUser.PERMISSION> perms = id == 1 ? Set.of(CREATE_USERS,DELETE_USERS,IMPERSONATE, MANAGE_LOGIN_SERVICES,LIST_USERS) : Set.of(); |
||||
return new DbUser( |
||||
id, |
||||
rs.getString(LOGIN), |
||||
rs.getString(EMAIL), |
||||
Password.of(rs.getString(PASS)), |
||||
rs.getString(THEME), |
||||
"de", // TODO: save in DB
|
||||
perms, |
||||
rs.getLong(LAST_LOGOFF) |
||||
); |
||||
} |
||||
|
||||
@Override |
||||
public ForeignLogin unlink(ForeignLogin assignment) throws UmbrellaException { |
||||
try { |
||||
Query.delete().from(TABLE_SERVICE_IDS_USERS) |
||||
.where(SERVICE_ID,equal(assignment.loginService()+":"+assignment.foreingId())) |
||||
.where(USER_ID,equal(assignment.userId())) |
||||
.execute(db); |
||||
return assignment; |
||||
} catch (SQLException e) { |
||||
LOG.log(WARNING,"Failed to drop foreign login assignment: {0} - {1}",assignment.loginService(),assignment.foreingId(),e); |
||||
throw new UmbrellaException(500,"Failed to drop foreign login assignment: {0} - {1}",assignment.loginService(),assignment.foreingId()).causedBy(e); |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue