Git Product home page Git Product logo

Comments (11)

anarsultanov avatar anarsultanov commented on June 15, 2024 1

The issue appears to be linked to internalization. After turning it on, I was able to reproduce the problem.

I'll dig deeper into this when I have some free time and your input could be valuable in finding a solution.
Since usually we invite a non-existent user, we can't determine their locale, therefore, one option could be using the default realm locale for invitations. Alternatively, we might think about using the sender's locale and/or improving the API to include locale as an option. What do you think about these solutions for your situation?

from keycloak-multi-tenancy.

anarsultanov avatar anarsultanov commented on June 15, 2024 1

In the new version with the latest changes, internationalization should work. I also added the ability to specify the locale of invitations.
@oleaasbo please check and if everything works for you, I will close the issue.

from keycloak-multi-tenancy.

oleaasbo avatar oleaasbo commented on June 15, 2024 1

Thank you @anarsultanov!
Can confirm that it works! My test only included turning internalization on again.
Keycloak-multi-tenancy v22.1.0
Keycloak v22.0.3

from keycloak-multi-tenancy.

anarsultanov avatar anarsultanov commented on June 15, 2024

Hi @oleaasbo,

Thank you for your feedback!

To customize the link in the invitation email, you have a few options here:

  1. You can follow the standard Keycloak email customization process by creating a custom theme . Within this theme, you can either customize the email template (invitation-email.ftl) or modify the email messages (invitationEmailBody and invitationEmailBodyHtml). This allows you to tailor the email content, including the link, to your specific needs.
  2. Alternatively, you can make changes in the extension source code by updating the same files, and then rebuild it.

Regarding your feature request, it's an interesting idea. However, please keep in mind that the extension doesn't have direct control over the application's behavior. Asking every user to provide their application URL for email customization and implementing a redirect back to Keycloak might not be the most user-friendly approach.

A potentially more elegant solution could be to introduce an endpoint within the extension to generate action tokens that allow users to join a tenant. Then, it would be up to individual applications to request and provide these tokens to invited users. While I can't promise immediate implementation, you're welcome to submit a pull request if you'd like to contribute to this feature's development.

If you have any more questions or suggestions, please don't hesitate to reach out.

from keycloak-multi-tenancy.

oleaasbo avatar oleaasbo commented on June 15, 2024

I like option 1, however I am not able to get it to work. I get an error when email should be sent.

2023-09-13 23:01:47,091 ERROR [org.keycloak.services] (executor-thread-3) KC-SERVICES0029: Failed to send email: org.keycloak.email.EmailException: Failed to template email
2023-09-14T01:01:47.094530966+02:00 	at org.keycloak.email.freemarker.FreeMarkerEmailTemplateProvider.processTemplate(FreeMarkerEmailTemplateProvider.java:242)
2023-09-14T01:01:47.094542358+02:00 	at org.keycloak.email.freemarker.FreeMarkerEmailTemplateProvider.send(FreeMarkerEmailTemplateProvider.java:257)
	at org.keycloak.email.freemarker.FreeMarkerEmailTemplateProvider.send(FreeMarkerEmailTemplateProvider.java:252)
2023-09-14T01:01:47.094557368+02:00 	at dev.sultanov.keycloak.multitenancy.util.EmailUtil.sendEmail(EmailUtil.java:50)
2023-09-14T01:01:47.094564008+02:00 	at dev.sultanov.keycloak.multitenancy.util.EmailUtil.sendInvitationEmail(EmailUtil.java:27)
	at dev.sultanov.keycloak.multitenancy.resource.TenantInvitationsResource.createInvitation(TenantInvitationsResource.java:73)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
2023-09-14T01:01:47.094595511+02:00 	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
2023-09-14T01:01:47.094609565+02:00 	at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:154)
2023-09-14T01:01:47.094615843+02:00 	at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:118)
2023-09-14T01:01:47.094622140+02:00 	at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:560)
2023-09-14T01:01:47.094628410+02:00 	at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:452)
2023-09-14T01:01:47.094634778+02:00 	at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:413)
2023-09-14T01:01:47.094641462+02:00 	at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:321)
	at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:415)
	at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:378)
2023-09-14T01:01:47.094674122+02:00 	at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:174)
	at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:142)
2023-09-14T01:01:47.094686789+02:00 	at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:168)
	at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:142)
2023-09-14T01:01:47.094699397+02:00 	at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:168)
	at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:131)
2023-09-14T01:01:47.094712221+02:00 	at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:33)
	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:429)
2023-09-14T01:01:47.094745726+02:00 	at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:240)
2023-09-14T01:01:47.094751991+02:00 	at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:154)
	at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:321)
	at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:157)
2023-09-14T01:01:47.094771284+02:00 	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:229)
2023-09-14T01:01:47.094777737+02:00 	at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:82)
2023-09-14T01:01:47.094783849+02:00 	at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:147)
	at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:84)
	at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:44)
2023-09-14T01:01:47.094845151+02:00 	at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
2023-09-14T01:01:47.094856938+02:00 	at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:177)
2023-09-14T01:01:47.094876798+02:00 	at io.vertx.ext.web.impl.RoutingContextWrapper.next(RoutingContextWrapper.java:200)
	at io.quarkus.vertx.http.runtime.options.HttpServerCommonHandlers$1.handle(HttpServerCommonHandlers.java:58)
	at io.quarkus.vertx.http.runtime.options.HttpServerCommonHandlers$1.handle(HttpServerCommonHandlers.java:36)
	at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
	at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:177)
2023-09-14T01:01:47.094938496+02:00 	at io.vertx.ext.web.impl.RoutingContextWrapper.next(RoutingContextWrapper.java:200)
2023-09-14T01:01:47.094947009+02:00 	at org.keycloak.quarkus.runtime.integration.web.QuarkusRequestFilter.lambda$createBlockingHandler$0(QuarkusRequestFilter.java:82)
2023-09-14T01:01:47.094954007+02:00 	at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:576)
	at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
	at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
	at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.UnsupportedOperationException
	at dev.sultanov.keycloak.multitenancy.util.EmailUtil$Recipient.getFirstAttribute(EmailUtil.java:128)
2023-09-14T01:01:47.095026494+02:00 	at org.keycloak.locale.DefaultLocaleSelectorProvider.getUserProfileSelection(DefaultLocaleSelectorProvider.java:108)
	at org.keycloak.locale.DefaultLocaleSelectorProvider.getUserLocale(DefaultLocaleSelectorProvider.java:71)
2023-09-14T01:01:47.095050287+02:00 	at org.keycloak.locale.DefaultLocaleSelectorProvider.resolveLocale(DefaultLocaleSelectorProvider.java:50)
	at org.keycloak.services.DefaultKeycloakContext.resolveLocale(DefaultKeycloakContext.java:133)
	at org.keycloak.email.freemarker.FreeMarkerEmailTemplateProvider.processTemplate(FreeMarkerEmailTemplateProvider.java:212)
2023-09-14T01:01:47.095070825+02:00 	... 50 more
2023-09-14T01:01:47.095077050+02:00 

I have created a custom theme and selected it under realm settings -> themes -> Email themes
/opt/keycloak/themes/tenant-invites/email/theme.properties

parent=base
import=common/keycloak

/opt/keycloak/themes/tenant-invites/email/messages/messages_en.properties

reviewInvitationsHeader=Review invitations
reviewInvitationsInfo=You have been invited to join the following tenants. Uncheck the box to decline the invitation.
selectTenantHeader=Select tenant
selectTenantInfo=Select the tenant you would like to log in to.
createTenantHeader=Create tenant
createTenantInfo=You need to create a tenant.
tenantName=Tenant name
tenantEmptyError=Please specify value.
tenantExistsError=Tenant already exists.
invitationEmailSubject=Invitation to {0}
invitationEmailBody=You have been invited to join {0}. To accept or decline this invitation, log in to your account or register using the link below.\n\n{1}
invitationEmailBodyHtml=<p>You have been invited to join {0}. To accept or decline this invitation, log in to your account or register using the link below.</p><p><a href="{1}">Link to account page</a></p>
invitationAcceptedEmailSubject=Your invitation has been accepted
invitationAcceptedEmailBody=Your invitation for {0} to join {1} has been accepted.
invitationAcceptedEmailBodyHtml=<p>Your invitation for {0} to join {1} has been accepted.</p>
invitationDeclinedEmailSubject=Your invitation has been declined
invitationDeclinedEmailBody=Your invitation for {0} to join {1} has been declined.
invitationDeclinedEmailBodyHtml=<p>Your invitation for {0} to join {1} has been declined.</p>

The messages are identical to the original (copied from keycloak-multi-tenancy/resources/theme-resources/messages/messages_en.properties)

What do i do wrong?
Thanks for the help!

from keycloak-multi-tenancy.

anarsultanov avatar anarsultanov commented on June 15, 2024

I'm sorry, but I'm not entirely sure what might be causing the issue in your specific case.

I attempted to create a Docker image with the extension and custom theme, and after configuring it in Realm, it was able to successfully use my custom email messages. Below is the Dockerfile I used for this, assuming the extension is located in the providers directory and the custom theme is in themes:

FROM quay.io/keycloak/keycloak:22.0.1 as builder
COPY /providers/ /opt/keycloak/providers/

RUN /opt/keycloak/bin/kc.sh build

FROM quay.io/keycloak/keycloak:22.0.1
COPY --from=builder /opt/keycloak/ /opt/keycloak/
COPY /themes/ /opt/keycloak/themes/

ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]

It's possible that it worked for me because I separated the build step. You might want to consider doing the same and building an optimized image if you haven't already.

from keycloak-multi-tenancy.

oleaasbo avatar oleaasbo commented on June 15, 2024

I did not separate the build stage like you did here but tried it with no different outcome.

FROM quay.io/keycloak/keycloak:latest as builder
ENV KC_CACHE=ispn
ENV KC_CACHE_STACK=kubernetes
ENV KC_TRANSACTION_XA_ENABLED=true
# Enable health and metrics support
ENV KC_HEALTH_ENABLED=true
ENV KC_METRICS_ENABLED=true

ENV KC_HTTP_RELATIVE_PATH=/auth/
ENV KC_FEATURES=docker,account-api,admin-api,authorization,client-policies

# Configure a database vendor
ENV KC_DB=postgres

# Organization plugin
COPY ./keycloak-multi-tenancy/target/keycloak-multi-tenancy-22.0.0.jar /opt/keycloak/providers
# Apple IdP plugin
ADD --chown=keycloak:keycloak https://github.com/klausbetz/apple-identity-provider-keycloak/releases/download/1.7.0/apple-identity-provider-1.7.0.jar /opt/keycloak/providers/apple-identity-provider-1.7.0.jar
RUN /opt/keycloak/bin/kc.sh build

FROM quay.io/keycloak/keycloak:latest
COPY --from=builder /opt/keycloak/ /opt/keycloak/
# Copy all themes
COPY --chown=keycloak:root ./themes /opt/keycloak/themes

ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "--spi-theme-static-max-age=-1", "--spi-theme-cache-themes=false", "--spi-theme-cache-templates=false"]

No matter what email theme i select i get the errors from earlier. Does not seem like i am able to deselect themes now after selecting one.
When you tested with a custom theme did you use the two files as described in my previous post?

from keycloak-multi-tenancy.

anarsultanov avatar anarsultanov commented on June 15, 2024

Yes, I added exactly two files as you described.
I'm guessing something else in your configuration is causing this issue. Could you try enabling debug logging and see if you can get more information?

from keycloak-multi-tenancy.

oleaasbo avatar oleaasbo commented on June 15, 2024

The logging did not provide additional information about this error. However the exception is thrown from:
keycloak-multi-tenancy/src/main/java/dev/sultanov/keycloak/multitenacy/util/EmailUtil.java

Caused by: java.lang.UnsupportedOperationException
at dev.sultanov.keycloak.multitenancy.util.EmailUtil$Recipient.getFirstAttribute(EmailUtil.java:128)

@Override
public String getFirstAttribute(String name) {
        throw new UnsupportedOperationException();
}

This is included in the error log from previous comment and did also appear now with KC_LOG_LEVEL="fatal,error,warn,info,debug"

from keycloak-multi-tenancy.

oleaasbo avatar oleaasbo commented on June 15, 2024

After disabling Internationalization we are back in business! Thank you so much for your help! Much appreciated.
I would love to see a combination of using the default realm locale if no locale is provided during the creation of the invite (Improving the API)

Going back to the feature request regarding action token invite link, I prefer you "elegant solution" of extending the API to allow me to send emails from my application. In this case the locale no longer relies on Keycloak settings and invites can easily be sent over other transports like SMS and social media, etc.

Should i open a new discussion about this feature or do you consider it relevant within this topic/discussion?

from keycloak-multi-tenancy.

anarsultanov avatar anarsultanov commented on June 15, 2024

I am glad to help!
I would prefer that you create a separate feature request for the invitation token as I want to improve the handling of internationalization within this issue and close it.

from keycloak-multi-tenancy.

Related Issues (18)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.