Both applications have implemented the db-per-tenant approach explained in this example: [url=]https://github.com/cuba-labs/db-per-tenant[/url] and discussed here: [url=]https://www.cuba-platform.com/discuss/t/cuba-multi-tenant-with-database-per-tenant[/url]
After logging in, Application A will display an extended AbstractMainWindow, where an user can choose from an optionsList the database which he wants to work with.
After choosing the database, the user clicks a button and a new tab is opened with the application B. On button click, I used the folowing code to open app B in a new tab.
This is working good, regarding SSO (it redirects to application B and no login page is displayed)
Also on button click, before opening app B with the code above, I have set the session attribute:
After this, the Session Attribute tenantDbAddress is present in application A but it’s not present in application B so the connection to the additional datastore is not made.
I want to ask you if this is a normal behavior considering that authentication is made from Application A?
If yes, which is the way to send the database parameter from app A to app B and set the session attribute to trigger the connection creation?
It is a really interesting system architecture. Thanks for sharing your experience!
First of all, let me clarify implementation details of IDP:
IDP SSO subsystem is developed to be useful not only for CUBA-based web applications. It can be used by any third party application that can use IDP as a simple web service to authenticate users.
IDP stores information about authentication using separate objects - IdpSession, it does not use standard UserSession.
IdpSession is a record about centralized authentication across applications. It is stored on a middle ware of a server that plays role of IDP.
If you use IDP with CUBA-based service provider then IdpSession ID is stored as a session attribute of UserSession object - “idpSessionId”.
IdpSession contains its own session attributes that can be updated only from a server with IDP role.
At the moment, there is no convenient API to update IdpSession attributes, but you can do it yourself:
Create middle ware service
Implement method that will obtain IdpSession for currently logged in user and update its attributes:
@Service(DemoIdpService.NAME)
public class DemoIdpServiceBean implements DemoIdpService {
@Inject
private IdpSessionStore idpSessionStore;
@Inject
private UserSessionSource userSessionSource;
@Override
public void setTenantToIdpSession(String tenant) {
UserSession userSession = userSessionSource.getUserSession();
String idpSessionId = userSession.getAttribute(IdpService.IDP_USER_SESSION_ATTRIBUTE);
if (idpSessionId != null) {
IdpSession idpSession = idpSessionStore.getSession(idpSessionId);
if (idpSession != null) {
Map<String, Serializable> attributes = idpSession.getAttributes();
if (attributes == null) {
attributes = new HashMap<>();
idpSession.setAttributes(attributes);
}
attributes.put("tenantDbAddress","localhost/dbpt_tenant1");
}
}
}
}
Execute this method in application with IDP role
There is only one drawback with this solution: if you use middle ware cluster for IDP application then it will not update cluster state with for IDP session attributes. This has been fixed in https://youtrack.cuba-platform.com/issue/PL-9312
IdpSession attributes can be loaded in a service provider using the following way:
Obtain IDP session id from UserSession
Perform HTTP Get request to IDP and read IDP session details
public class ExtAppMainWindow extends AppMainWindow {
@Inject
private UserSession userSession;
@Inject
private WebAuthConfig webAuthConfig;
@Override
public void init(Map<String, Object> params) {
super.init(params);
String idpSessionId = userSession.getAttribute(IdpService.IDP_USER_SESSION_ATTRIBUTE);
// now we have to use HTTP request to get whole session object from IDP
// see com.haulmont.idp.controllers.IdpServiceController
String idpBaseURL = webAuthConfig.getIdpBaseURL();
if (!idpBaseURL.endsWith("/")) {
idpBaseURL += "/";
}
String idpSessionGetUrl = idpBaseURL + "service/get";
URI sessionGetUri;
try {
sessionGetUri = new URIBuilder(idpSessionGetUrl)
.addParameter("idpSessionId", idpSessionId)
.addParameter("trustedServicePassword", webAuthConfig.getIdpTrustedServicePassword())
.build();
} catch (URISyntaxException e) {
throw new RuntimeException("incorrect URL");
}
HttpGet httpGet = new HttpGet(sessionGetUri);
HttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager();
HttpClient client = HttpClientBuilder.create()
.setConnectionManager(connectionManager)
.build();
String idpSessionJson;
try {
HttpResponse httpResponse = client.execute(httpGet);
int statusCode = httpResponse.getStatusLine().getStatusCode();
if (statusCode != 200) {
// error handling
}
idpSessionJson = new BasicResponseHandler()
.handleResponse(httpResponse);
} catch (IOException e) {
// error handling
} finally {
connectionManager.shutdown();
}
// now we can parse idpSessionJson content and read session attributes
}
}
I tested the demo and i see the attribute set on idpsession on idp app.
If i open the sp app I get the error bellow.
I’ve attached both archives
Thank you
29-Jun-2017 21:21:24.111 SEVERE [http-nio-8082-exec-26] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [app_servlet] in context with path [/app] threw exception
java.lang.RuntimeException: Unable to invoke no-args constructor for interface java.io.Serializable. Register an InstanceCreator with Gson for this type may fix this problem.
at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:226)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:210)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:41)
at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:187)
at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:145)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
at com.google.gson.Gson.fromJson(Gson.java:887)
at com.google.gson.Gson.fromJson(Gson.java:852)
at com.google.gson.Gson.fromJson(Gson.java:801)
at com.google.gson.Gson.fromJson(Gson.java:773)
at com.haulmont.cuba.web.auth.IdpAuthProvider.getIdpSession(IdpAuthProvider.java:243)
at com.haulmont.cuba.web.auth.IdpAuthProvider.doFilter(IdpAuthProvider.java:151)
at com.haulmont.cuba.web.sys.CubaHttpFilter.filterByAuthProvider(CubaHttpFilter.java:114)
at com.haulmont.cuba.web.sys.CubaHttpFilter.doFilter(CubaHttpFilter.java:91)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:474)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:624)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:349)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:783)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:789)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1437)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.UnsupportedOperationException: Interface can't be instantiated! Interface name: java.io.Serializable
at com.google.gson.internal.UnsafeAllocator.assertInstantiable(UnsafeAllocator.java:117)
at com.google.gson.internal.UnsafeAllocator.access$000(UnsafeAllocator.java:31)
at com.google.gson.internal.UnsafeAllocator$1.newInstance(UnsafeAllocator.java:49)
at com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:223)
I’ve reproduced the issue. It is caused by our usage of Gson library that can perfectly deserialize Map<String, Object> but fails with Map<String, Serializable>. I’ll fix it for the upcoming 6.6 release, see this issue: https://youtrack.cuba-platform.com/issue/PL-9401