Browse Source

implemented:

- altering of mail settings
- sending email

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
sqlite
Stephan Richter 4 months ago
parent
commit
31afced7f7
  1. 1
      de.srsoftware.oidc.api/build.gradle
  2. 3
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Constants.java
  3. 8
      de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/MailConfig.java
  4. 26
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/EmailController.java
  5. 9
      de.srsoftware.oidc.backend/src/main/java/de/srsoftware/oidc/backend/UserController.java
  6. 1
      de.srsoftware.oidc.datastore.file/build.gradle
  7. 51
      de.srsoftware.oidc.datastore.file/src/main/java/de/srsoftware/oidc/datastore/file/FileStore.java
  8. 4
      de.srsoftware.oidc.web/src/main/resources/en/scripts/common.js
  9. 41
      de.srsoftware.oidc.web/src/main/resources/en/scripts/settings.js
  10. 25
      de.srsoftware.oidc.web/src/main/resources/en/settings.html

1
de.srsoftware.oidc.api/build.gradle

@ -13,6 +13,7 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.junit.jupiter:junit-jupiter'
implementation 'org.json:json:20240303' implementation 'org.json:json:20240303'
implementation 'org.bitbucket.b_c:jose4j:0.9.6' implementation 'org.bitbucket.b_c:jose4j:0.9.6'
implementation 'com.sun.mail:jakarta.mail:2.0.1'
implementation project(':de.srsoftware.utils') implementation project(':de.srsoftware.utils')
} }

3
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/Constants.java

@ -37,7 +37,8 @@ public class Constants {
public static final String RESPONSE_TYPE = "response_type"; public static final String RESPONSE_TYPE = "response_type";
public static final String SCOPE = "scope"; public static final String SCOPE = "scope";
public static final String SECRET = "secret"; public static final String SECRET = "secret";
public static final String SENDER_ADDRESS = "sender_address"; public static final String SMTP_USER = "smtp_user";
public static final String SMTP_PASSWORD = "smtp_pass";
public static final String SMTP_AUTH = "smtp_auth"; public static final String SMTP_AUTH = "smtp_auth";
public static final String SMTP_HOST = "smtp_host"; public static final String SMTP_HOST = "smtp_host";
public static final String SMTP_PORT = "smtp_port"; public static final String SMTP_PORT = "smtp_port";

8
de.srsoftware.oidc.api/src/main/java/de/srsoftware/oidc/api/MailConfig.java

@ -3,6 +3,7 @@ package de.srsoftware.oidc.api;
import static de.srsoftware.oidc.api.Constants.*; import static de.srsoftware.oidc.api.Constants.*;
import jakarta.mail.Authenticator;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
@ -35,6 +36,7 @@ public interface MailConfig {
props.put("mail.smtp.port", smtpPort()); props.put("mail.smtp.port", smtpPort());
props.put("mail.smtp.auth", smtpAuth() ? "true" : "false"); props.put("mail.smtp.auth", smtpAuth() ? "true" : "false");
props.put("mail.smtp.starttls.enable", startTls() ? "true" : "false"); props.put("mail.smtp.starttls.enable", startTls() ? "true" : "false");
props.put("mail.smtp.ssl.trust", smtpHost());
return props; return props;
} }
@ -43,7 +45,11 @@ public interface MailConfig {
SMTP_HOST, smtpHost(), // SMTP_HOST, smtpHost(), //
SMTP_PORT, smtpPort(), // SMTP_PORT, smtpPort(), //
SMTP_AUTH, smtpAuth(), // SMTP_AUTH, smtpAuth(), //
SENDER_ADDRESS, senderAddress(), // SMTP_USER, senderAddress(), //
START_TLS, startTls()); START_TLS, startTls());
} }
Authenticator authenticator();
MailConfig save();
} }

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

@ -1,6 +1,7 @@
/* © SRSoftware 2024 */ /* © SRSoftware 2024 */
package de.srsoftware.oidc.backend; package de.srsoftware.oidc.backend;
import static de.srsoftware.oidc.api.Constants.*;
import static de.srsoftware.oidc.api.data.Permission.MANAGE_SMTP; import static de.srsoftware.oidc.api.data.Permission.MANAGE_SMTP;
import static java.net.HttpURLConnection.HTTP_FORBIDDEN; import static java.net.HttpURLConnection.HTTP_FORBIDDEN;
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
@ -31,8 +32,33 @@ public class EmailController extends Controller {
return notFound(ex); return notFound(ex);
} }
@Override
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();
switch (path) {
case "/settings":
return saveSettings(ex, user);
}
return notFound(ex);
}
private boolean provideSettings(HttpExchange ex, User user) throws IOException { private boolean provideSettings(HttpExchange ex, User user) throws IOException {
if (!user.hasPermission(MANAGE_SMTP)) return sendEmptyResponse(HTTP_FORBIDDEN, ex); if (!user.hasPermission(MANAGE_SMTP)) return sendEmptyResponse(HTTP_FORBIDDEN, ex);
return sendContent(ex, mailConfig.map()); return sendContent(ex, mailConfig.map());
} }
private boolean saveSettings(HttpExchange ex, User user) throws IOException {
if (!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));
if (data.has(SMTP_USER)) mailConfig.senderAddress(data.getString(SMTP_USER));
if (data.has(SMTP_PASSWORD)) mailConfig.senderPassword(data.getString(SMTP_PASSWORD));
if (data.has(SMTP_AUTH)) mailConfig.smtpAuth(data.getBoolean(SMTP_AUTH));
if (data.has(START_TLS)) mailConfig.startTls(data.getBoolean(START_TLS));
mailConfig.save();
return sendContent(ex, "saved");
}
} }

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

@ -22,18 +22,11 @@ import org.json.JSONObject;
public class UserController extends Controller { public class UserController extends Controller {
private final UserService users; private final UserService users;
private final MailConfig mailConfig; private final MailConfig mailConfig;
private final Authenticator auth;
public UserController(MailConfig mailConfig, SessionService sessionService, UserService userService) { public UserController(MailConfig mailConfig, SessionService sessionService, UserService userService) {
super(sessionService); super(sessionService);
users = userService; users = userService;
this.mailConfig = mailConfig; this.mailConfig = mailConfig;
auth = new Authenticator() {
// override the getPasswordAuthentication method
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(mailConfig.senderAddress(), mailConfig.senderPassword());
}
};
} }
private boolean addUser(HttpExchange ex, Session session) throws IOException { private boolean addUser(HttpExchange ex, Session session) throws IOException {
@ -127,7 +120,7 @@ public class UserController extends Controller {
private void senPasswordLink(User user) { private void senPasswordLink(User user) {
LOG.log(WARNING, "Sending password link to {0}", user.email()); LOG.log(WARNING, "Sending password link to {0}", user.email());
try { try {
var session = jakarta.mail.Session.getDefaultInstance(mailConfig.props(), auth); var session = jakarta.mail.Session.getDefaultInstance(mailConfig.props(), mailConfig.authenticator());
Message message = new MimeMessage(session); Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(mailConfig.senderAddress())); message.setFrom(new InternetAddress(mailConfig.senderAddress()));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(user.email())); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(user.email()));

1
de.srsoftware.oidc.datastore.file/build.gradle

@ -16,6 +16,7 @@ dependencies {
implementation project(':de.srsoftware.utils') implementation project(':de.srsoftware.utils')
implementation 'org.json:json:20240303' implementation 'org.json:json:20240303'
implementation 'org.bitbucket.b_c:jose4j:0.9.6' implementation 'org.bitbucket.b_c:jose4j:0.9.6'
implementation 'com.sun.mail:jakarta.mail:2.0.1'
} }

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

@ -9,6 +9,8 @@ import static java.util.Optional.empty;
import de.srsoftware.oidc.api.*; import de.srsoftware.oidc.api.*;
import de.srsoftware.oidc.api.data.*; import de.srsoftware.oidc.api.data.*;
import jakarta.mail.Authenticator;
import jakarta.mail.PasswordAuthentication;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
@ -37,6 +39,7 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
private Map<String, Client> clients = new HashMap<>(); private Map<String, Client> clients = new HashMap<>();
private Map<String, User> accessTokens = new HashMap<>(); private Map<String, User> accessTokens = new HashMap<>();
private Map<String, Authorization> authCodes = new HashMap<>(); private Map<String, Authorization> authCodes = new HashMap<>();
private Authenticator auth;
public FileStore(File storage, PasswordHasher<String> passwordHasher) throws IOException { public FileStore(File storage, PasswordHasher<String> passwordHasher) throws IOException {
this.storageFile = storage.toPath(); this.storageFile = storage.toPath();
@ -48,9 +51,10 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
Files.writeString(storageFile, "{}"); Files.writeString(storageFile, "{}");
} }
json = new JSONObject(Files.readString(storageFile)); json = new JSONObject(Files.readString(storageFile));
auth = null; // lazy init!
} }
private FileStore save() { public FileStore save() {
try { try {
Files.writeString(storageFile, json.toString(2)); Files.writeString(storageFile, json.toString(2));
return this; return this;
@ -319,6 +323,19 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
/*** MailConfig implementation ***/ /*** MailConfig implementation ***/
@Override
public Authenticator authenticator() {
if (auth == null) {
auth = new Authenticator() {
// override the getPasswordAuthentication method
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(senderAddress(), senderPassword());
}
};
}
return auth;
}
private String mailConfig(String key) { private String mailConfig(String key) {
var config = json.getJSONObject(MAILCONFIG); var config = json.getJSONObject(MAILCONFIG);
if (config.has(key)) return config.getString(key); if (config.has(key)) return config.getString(key);
@ -328,71 +345,71 @@ public class FileStore implements AuthorizationService, ClientService, SessionSe
private FileStore mailConfig(String key, Object newValue) { private FileStore mailConfig(String key, Object newValue) {
var config = json.getJSONObject(MAILCONFIG); var config = json.getJSONObject(MAILCONFIG);
config.put(key, newValue); config.put(key, newValue);
auth = null;
return this; return this;
} }
@Override @Override
public String smtpHost() { public String smtpHost() {
return mailConfig("smtp_host"); return mailConfig(SMTP_HOST);
} }
@Override @Override
public MailConfig smtpHost(String newValue) { public MailConfig smtpHost(String newValue) {
return mailConfig("smtp_host", newValue); return mailConfig(SMTP_HOST, newValue);
} }
@Override @Override
public int smtpPort() { public int smtpPort() {
try { var config = json.getJSONObject(MAILCONFIG);
return Integer.parseInt(mailConfig("smtp_port")); return config.has(SMTP_PORT) ? config.getInt(SMTP_PORT) : 0;
} catch (NumberFormatException nfe) {
return 0;
}
} }
@Override @Override
public MailConfig smtpPort(int newValue) { public MailConfig smtpPort(int newValue) {
return mailConfig("smtp_port", newValue); return mailConfig(SMTP_PORT, newValue);
} }
@Override @Override
public String senderAddress() { public String senderAddress() {
return mailConfig("sender_address"); return mailConfig(SMTP_USER);
} }
@Override @Override
public MailConfig senderAddress(String newValue) { public MailConfig senderAddress(String newValue) {
return mailConfig("sender_address", newValue); return mailConfig(SMTP_USER, newValue);
} }
@Override @Override
public String senderPassword() { public String senderPassword() {
return mailConfig("smtp_password"); return mailConfig(SMTP_PASSWORD);
} }
@Override @Override
public MailConfig senderPassword(String newValue) { public MailConfig senderPassword(String newValue) {
return mailConfig("smtp_password", newValue); return mailConfig(SMTP_PASSWORD, newValue);
} }
@Override @Override
public boolean startTls() { public boolean startTls() {
return "true".equals(mailConfig("start_tls")); var config = json.getJSONObject(MAILCONFIG);
return config.has(START_TLS) ? config.getBoolean(START_TLS) : false;
} }
@Override @Override
public MailConfig startTls(boolean newValue) { public MailConfig startTls(boolean newValue) {
return mailConfig("start_tls", newValue); return mailConfig(START_TLS, newValue);
} }
@Override @Override
public boolean smtpAuth() { public boolean smtpAuth() {
return "true".equals(mailConfig("smtp_auth")); var config = json.getJSONObject(MAILCONFIG);
return config.has(SMTP_AUTH) ? config.getBoolean(SMTP_AUTH) : false;
} }
@Override @Override
public MailConfig smtpAuth(boolean newValue) { public MailConfig smtpAuth(boolean newValue) {
return mailConfig("smtp_auth", newValue); return mailConfig(SMTP_AUTH, newValue);
} }
} }

4
de.srsoftware.oidc.web/src/main/resources/en/scripts/common.js

@ -25,6 +25,10 @@ function hide(id){
get(id).style.display = 'none'; get(id).style.display = 'none';
} }
function isChecked(id){
return get(id).checked;
}
function login(){ function login(){
redirect('login.html?return_to='+encodeURIComponent(window.location.href)); redirect('login.html?return_to='+encodeURIComponent(window.location.href));
} }

41
de.srsoftware.oidc.web/src/main/resources/en/scripts/settings.js

@ -28,6 +28,24 @@ async function handlePasswordResponse(response){
},10000); },10000);
} }
async function handleSmtpResponse(response){
if (response.ok){
hide('wrong_password');
hide('password_mismatch');
setText('smtpBtn', 'saved.');
} else {
setText('smtpBtn', 'Update failed!');
var text = await response.text();
if (text == 'wrong password') show('wrong_password');
if (text == 'password mismatch') show('password_mismatch');
}
setTimeout(function(){
enable('smtpBtn');
setText('smtpBtn','Update');
},10000);
}
function handleResponse(response){ function handleResponse(response){
if (response.ok){ if (response.ok){
hide('update_error') hide('update_error')
@ -36,8 +54,8 @@ function handleResponse(response){
show('update_error'); show('update_error');
setText('updateBtn', 'Update failed!'); setText('updateBtn', 'Update failed!');
} }
enable('updateBtn');
setTimeout(function(){ setTimeout(function(){
enable('updateBtn');
setText('updateBtn','Update'); setText('updateBtn','Update');
},10000); },10000);
} }
@ -49,6 +67,8 @@ async function handleSettings(response){
for (var key in json){ for (var key in json){
setValue(key,json[key]); setValue(key,json[key]);
} }
get('start_tls').checked = json.start_tls;
get('smtp_auth').checked = json.smtp_auth;
show('mail_settings'); show('mail_settings');
} else { } else {
hide('mail_settings'); hide('mail_settings');
@ -59,6 +79,25 @@ function passKeyDown(ev){
if (event.keyCode == 13) updatePass(); if (event.keyCode == 13) updatePass();
} }
function updateSmtp(){
disable('smtpBtn');
var newData = {
smtp_host : getValue('smtp_host'),
smtp_port : getValue('smtp_port'),
smtp_user : getValue('smtp_user'),
smtp_pass : getValue('smtp_pass'),
smtp_auth : isChecked('smtp_auth'),
start_tls : isChecked('start_tls')
}
fetch("/api/email/settings",{
method : 'POST',
headers : {
'Content-Type': 'application/json'
},
body : JSON.stringify(newData)
}).then(handleSmtpResponse);
setText('smtpBtn','sent…');
}
function updatePass(){ function updatePass(){

25
de.srsoftware.oidc.web/src/main/resources/en/settings.html

@ -78,6 +78,7 @@
</tr> </tr>
</table> </table>
</fieldset> </fieldset>
<br/>
<fieldset id="mail_settings" style="display: none"> <fieldset id="mail_settings" style="display: none">
<legend> <legend>
Mail settings Mail settings
@ -85,20 +86,32 @@
<table> <table>
<tr> <tr>
<th>Smtp host</th> <th>Smtp host</th>
<td><input type="text" id="smtp_host"></td> <td><input type="text" id="smtp_host" placeholder="smtp host"></td>
</tr> </tr>
<tr> <tr>
<th>Smtp port</th> <th>Smtp port</th>
<td><input type="text" id="smtp_port"></td> <td><input type="number" id="smtp_port"></td>
</tr> </tr>
<tr> <tr>
<th>Sender email address</th> <th>Smtp user</th>
<td><input type="text" id="sender_mail"></td> <td><input type="text" id="smtp_user" placeholder="smtp user"></td>
</tr> </tr>
<tr> <tr>
<th>Sender password</th> <th>Smtp password</th>
<td><input type="password" id="sender_password"></td> <td><input type="password" id="smtp_pass" placeholder="password"></td>
</tr> </tr>
<tr>
<th>Security</th>
<td>
<label>
<input type="checkbox" id="smtp_auth"> Auth
</label>
<label>
<input type="checkbox" id="start_tls"> StartTLS
</label>
</td>
</tr>
<tr>
<td></td> <td></td>
<td><button id="smtpBtn" type="button" onClick="updateSmtp()">Update</button></td> <td><button id="smtpBtn" type="button" onClick="updateSmtp()">Update</button></td>
</tr> </tr>

Loading…
Cancel
Save