-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
added overridable email sender impl #41
Merged
Merged
Changes from 11 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
a8b32ba
added overridable email sender impl
xgp d29d858
proper property name parsing
xgp 655be14
removed ServerInfoAware impl because it leaks the config to other realms
xgp b23a24a
added docs to README
xgp d28d5e2
updated docs
xgp 7684574
added cache counter for max emails
xgp 31bb37a
cache key includes date for 1 day expiration
xgp 31b9eb6
sample of cache xml. failsafe without cache configured.
xgp fae3b12
merge
xgp e34dc4c
formatting in readme. changed condition name to useRealmConfig for cl…
xgp aab888e
space between variables in dockerfile
xgp 35655f7
Merge branch 'main' into xgp/override-email-sender
xgp File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<infinispan | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="urn:infinispan:config:15.0 http://www.infinispan.org/schemas/infinispan-config-15.0.xsd" | ||
xmlns="urn:infinispan:config:15.0"> | ||
|
||
<cache-container name="keycloak"> | ||
<transport lock-timeout="60000" stack="udp"/> | ||
<local-cache name="realms" simple-cache="true"> | ||
<encoding> | ||
<key media-type="application/x-java-object"/> | ||
<value media-type="application/x-java-object"/> | ||
</encoding> | ||
<memory max-count="10000"/> | ||
</local-cache> | ||
<local-cache name="users" simple-cache="true"> | ||
<encoding> | ||
<key media-type="application/x-java-object"/> | ||
<value media-type="application/x-java-object"/> | ||
</encoding> | ||
<memory max-count="10000"/> | ||
</local-cache> | ||
<distributed-cache name="sessions" owners="1"> | ||
<expiration lifespan="-1"/> | ||
<memory max-count="10000"/> | ||
</distributed-cache> | ||
<distributed-cache name="authenticationSessions" owners="2"> | ||
<expiration lifespan="-1"/> | ||
</distributed-cache> | ||
<distributed-cache name="offlineSessions" owners="1"> | ||
<expiration lifespan="-1"/> | ||
<memory max-count="10000"/> | ||
</distributed-cache> | ||
<distributed-cache name="clientSessions" owners="1"> | ||
<expiration lifespan="-1"/> | ||
<memory max-count="10000"/> | ||
</distributed-cache> | ||
<distributed-cache name="offlineClientSessions" owners="1"> | ||
<expiration lifespan="-1"/> | ||
<memory max-count="10000"/> | ||
</distributed-cache> | ||
<distributed-cache name="loginFailures" owners="2"> | ||
<expiration lifespan="-1"/> | ||
</distributed-cache> | ||
<local-cache name="authorization" simple-cache="true"> | ||
<encoding> | ||
<key media-type="application/x-java-object"/> | ||
<value media-type="application/x-java-object"/> | ||
</encoding> | ||
<memory max-count="10000"/> | ||
</local-cache> | ||
<replicated-cache name="work"> | ||
<expiration lifespan="-1"/> | ||
</replicated-cache> | ||
<!-- custom for counters --> | ||
<replicated-cache name="counterCache"> | ||
<expiration lifespan="-1"/> | ||
</replicated-cache> | ||
<local-cache name="keys" simple-cache="true"> | ||
<encoding> | ||
<key media-type="application/x-java-object"/> | ||
<value media-type="application/x-java-object"/> | ||
</encoding> | ||
<expiration max-idle="3600000"/> | ||
<memory max-count="1000"/> | ||
</local-cache> | ||
<distributed-cache name="actionTokens" owners="2"> | ||
<encoding> | ||
<key media-type="application/x-java-object"/> | ||
<value media-type="application/x-java-object"/> | ||
</encoding> | ||
<expiration max-idle="-1" lifespan="-1" interval="300000"/> | ||
<memory max-count="-1"/> | ||
</distributed-cache> | ||
</cache-container> | ||
</infinispan> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,4 +11,5 @@ services: | |
- 8080:8080 | ||
volumes: | ||
- ./target/keycloak-themes-0.34-SNAPSHOT.jar:/opt/keycloak/providers/keycloak-themes.jar | ||
command: [ "start-dev --spi-email-template-provider=freemarker-plus-mustache --spi-email-template-freemarker-plus-mustache-enabled=true" ] | ||
- ./conf/cache-ispn-custom.xml:/opt/keycloak/conf/cache-ispn-custom.xml | ||
entrypoint: /opt/keycloak/bin/kc.sh --verbose start-dev --cache-config-file=cache-ispn-custom.xml --spi-email-template-provider=freemarker-plus-mustache --spi-email-template-freemarker-plus-mustache-enabled=true --spi-email-sender-provider=ext-email-override --spi-email-sender-ext-email-override-enabled=true --spi-email-sender-ext-email-override-host=smtp.someserver.com --spi-email-sender-ext-email-override-auth=true [email protected] --spi-email-sender-ext-email-override-port=587 --spi-email-sender-ext-email-override-starttls=true --spi-email-sender-ext-email-override-user=someuser --spi-email-sender-ext-email-override-password=somepass --spi-email-sender-ext-email-override-max-emails=200 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
104 changes: 104 additions & 0 deletions
104
src/main/java/io/phasetwo/keycloak/email/OverridableEmailSenderProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package io.phasetwo.keycloak.email; | ||
|
||
import java.text.SimpleDateFormat; | ||
import java.util.Date; | ||
import java.util.Map; | ||
import java.util.concurrent.TimeUnit; | ||
import lombok.extern.jbosslog.JBossLog; | ||
import org.infinispan.Cache; | ||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider; | ||
import org.keycloak.email.DefaultEmailSenderProvider; | ||
import org.keycloak.email.EmailException; | ||
import org.keycloak.models.KeycloakSession; | ||
import org.keycloak.models.UserModel; | ||
|
||
@JBossLog | ||
public class OverridableEmailSenderProvider extends DefaultEmailSenderProvider { | ||
|
||
private final KeycloakSession session; | ||
private final Map<String, String> conf; | ||
private final Integer maxEmails; | ||
private final String cacheKey; | ||
private Cache<String, Integer> counterCache; | ||
|
||
public OverridableEmailSenderProvider( | ||
KeycloakSession session, Map<String, String> conf, Integer maxEmails) { | ||
super(session); | ||
this.session = session; | ||
this.conf = conf; | ||
this.maxEmails = maxEmails; | ||
this.cacheKey = getCacheKey(); | ||
try { | ||
this.counterCache = | ||
session.getProvider(InfinispanConnectionProvider.class).getCache("counterCache", true); | ||
} catch (Exception e) { | ||
log.warnf("Error loading counterCache %s", e); | ||
} | ||
} | ||
|
||
private boolean useRealmConfig(Map<String, String> config) { | ||
return (!config.isEmpty() && config.containsKey("host")); | ||
} | ||
|
||
private String getCacheKey() { | ||
if (session.getContext().getRealm() != null) { | ||
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); | ||
return String.format( | ||
"ext-email-override-emailCounter-%s-%s", | ||
session.getContext().getRealm().getName(), formatter.format(new Date())); | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
private boolean canSend() { | ||
if (cacheKey == null) return true; | ||
Integer count = counterCache.get(cacheKey); | ||
log.infof("Count for %s is %d / %d", cacheKey, count, maxEmails); | ||
if (count == null || count <= maxEmails) return true; | ||
else return false; | ||
} | ||
|
||
private Integer increment() { | ||
if (cacheKey == null) return 0; | ||
else | ||
return counterCache.compute( | ||
cacheKey, (key, value) -> (value == null) ? 1 : value + 1, 1, TimeUnit.DAYS); | ||
} | ||
|
||
@Override | ||
public void send( | ||
Map<String, String> config, UserModel user, String subject, String textBody, String htmlBody) | ||
throws EmailException { | ||
if (useRealmConfig(config)) { | ||
log.debug("Using customer override email sender"); | ||
super.send(config, user, subject, textBody, htmlBody); | ||
} else { | ||
if (canSend()) { | ||
super.send(conf, user, subject, textBody, htmlBody); | ||
Integer count = increment(); | ||
log.infof("Email count %d for %s", count, cacheKey); | ||
} else { | ||
log.infof("Unable to send email for limit %d %s", maxEmails, cacheKey); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public void send( | ||
Map<String, String> config, String address, String subject, String textBody, String htmlBody) | ||
throws EmailException { | ||
if (useRealmConfig(config)) { | ||
log.debug("Using customer override email sender"); | ||
super.send(config, address, subject, textBody, htmlBody); | ||
} else { | ||
if (canSend()) { | ||
super.send(conf, address, subject, textBody, htmlBody); | ||
Integer count = increment(); | ||
rtufisi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
log.infof("Email count %d for %s", count, cacheKey); | ||
} else { | ||
log.infof("Unable to send email for limit %d %s", maxEmails, cacheKey); | ||
} | ||
} | ||
} | ||
} |
70 changes: 70 additions & 0 deletions
70
src/main/java/io/phasetwo/keycloak/email/OverridableEmailSenderProviderFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package io.phasetwo.keycloak.email; | ||
|
||
import com.google.auto.service.AutoService; | ||
import com.google.common.base.Strings; | ||
import com.google.common.collect.ImmutableMap; | ||
import java.util.Map; | ||
import lombok.extern.jbosslog.JBossLog; | ||
import org.keycloak.Config; | ||
import org.keycloak.email.EmailSenderProviderFactory; | ||
import org.keycloak.models.KeycloakSession; | ||
import org.keycloak.models.KeycloakSessionFactory; | ||
|
||
@JBossLog | ||
@AutoService(EmailSenderProviderFactory.class) | ||
public class OverridableEmailSenderProviderFactory implements EmailSenderProviderFactory { | ||
|
||
private Integer maxEmails; | ||
private Map<String, String> conf; | ||
|
||
@Override | ||
public OverridableEmailSenderProvider create(KeycloakSession session) { | ||
return new OverridableEmailSenderProvider(session, conf, maxEmails); | ||
} | ||
|
||
public static final String[] PROPERTY_NAMES = { | ||
"host", | ||
"auth", | ||
"ssl", | ||
"starttls", | ||
"port", | ||
"from", | ||
"fromDisplayName", | ||
"replyTo", | ||
"replyToDisplayName", | ||
"envelopeFrom", | ||
"user", | ||
"password" | ||
}; | ||
|
||
@Override | ||
public void init(Config.Scope config) { | ||
log.info("Initializing config for email sender."); | ||
this.maxEmails = config.getInt("maxEmails", 100); | ||
log.infof("maxEmails set to %d", this.maxEmails); | ||
String host = config.get("host"); | ||
if (!Strings.isNullOrEmpty(host)) { // TODO better test than this | ||
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); | ||
for (String name : PROPERTY_NAMES) { | ||
String v = config.get(name); | ||
if (v != null) { | ||
builder.put(name, v); | ||
} | ||
} | ||
this.conf = builder.build(); | ||
} else { | ||
this.conf = ImmutableMap.of(); | ||
} | ||
} | ||
|
||
@Override | ||
public void postInit(KeycloakSessionFactory factory) {} | ||
|
||
@Override | ||
public void close() {} | ||
|
||
@Override | ||
public String getId() { | ||
return "ext-email-override"; | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure i understood this condition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is "should we use the realm config?"