Keycloak: Add custom extension io.undertow.servlet.ServletExtension - keycloak

I wanted to add a custom servlet extension to Keycloak which would install a http handler that gets invoked on every request sent to Keycloak and sets up some logging MDC context that our custom SPI code can use for logging the incoming request traces correctly.
Following the docs here I created a custom extension class:
public class UndertowHandlerExtension implements ServletExtension {
#Override
public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) {
deploymentInfo.addInnerHandlerChainWrapper(TraceIdCapturingHandler::new);
}
}
And have defined my custom http handler TraceIdCapturingHandler in the same JAR file. I also added a file to META-INF/services/io.undertow.servlet.ServletExtension and set the fully qualified reference to the extension class. I also updated my deployments jboss-deployment-structure.xml and added the following 2 entries as dependencies:
<module name="io.undertow.servlet" />
<module name="javax.servlet.api" />
However, when my deployment is created the extension is not being invoked and my filter is not executing. Is there something I am missing in terms of how to configure Wildfly for Keycloak so that my extension and handler are installed and used correctly?
EDIT:
After doing a bit of digging I realized I was headed down the wrong path. Looked at this repository and I think I need a custom RealResourceProvider as shown here which in turn can install my filter by obtaining an instance of ResteasyProviderFactory and invoking getContainerRequestFilterRegistry().registerSingleton().
Will try this out and report back.

Please see the edit above for my question. I was able to implement a RealmResourceProviderFactory instance that initialized the filters I needed on startup in the init() method:
#Override
public void init(Config.Scope scope) {
log.info("Initializing");
initializeKeycloakFilters();
}
private void initializeKeycloakFilters() {
ResteasyProviderFactory providerFactory = ResteasyProviderFactory.getInstance();
TraceIdCapturingFilter filter = new TraceIdCapturingFilter();
providerFactory.getContainerRequestFilterRegistry().registerSingleton(filter);
}

Related

How to add a custom Credential Store to WildFly

Using steps from https://docs.wildfly.org/23/WildFly_Elytron_Security.html#Custom_CredentialStore
Created a SPI and Provider implementation. For now, just simple implementation with logs to see if it works.
Now I don't know how to add this do WildFly.
I packaged it into a module and:
tried to add a <extension module=...> ref on standalone.xml, but than it complains that it is not an extension;
tried to add as subsystem=domain:ee/global-modules/module, there is no error, but nor SPI or Provider have a hit;
tried to add as subsystem=elytron/provider-loader, then Provider is called (twice ??), but SPI not.
So, using provider-loader, how to use my custom provider?
Here a snippet of Provider impl:
// used WildFlyElytronCredentialStoreProvider as reference
public class TestCredentialStoreProvider extends WildFlyElytronBaseProvider {
private static final TestCredentialStoreProvider INSTANCE = new TestCredentialStoreProvider ();
public TestCredentialStoreProvider () {
super("TestCredentialStoreProvider ", "1.0", "Test CredentialStore Provider");
putService(new Service(this, "CredentialStore", "TestCredentialStore", "package.TestCredentialStore", emptyList, emptyMap));
}
public static TestCredentialStoreProvider getInstance() {
return INSTANCE;
}
}
Obs. Why provider is loaded twice?
Create a jar and containing your credential store and provider classes, and add it as a WildFly module with a dependency on org.wildfly.security.elytron. For example:
module add --name=org.wildfly.customcredstore --resources=/path/to/customcredstoreprovider.jar --dependencies=org.wildfly.security.elytron
Create a provider loader for your provider. For example:
/subsystem=elytron/provider-loader=myProviderLoader:add(class-names=[org.wildfly.security.mycustomcredstore.CustomProvider],module=org.wildfly.customcredstore)
You can add it to the list of initial providers and reload the server
/subsystem=elytron:write-attribute(name=initial-providers,value=myProviderLoader)
reload
You can check loaded providers:
/subsystem=elytron/provider-loader=myProviderLoader:read-attribute(name=loaded-providers)
Then to add a custom credential store with the provider you can use:
/subsystem=elytron/credential-store=mystore:add(providers=myProviderLoader,type=TestCredentialStore,credential-reference={clear-text='pass'})
There is also some docs on how to add custom elytron component here: https://docs.wildfly.org/26/WildFly_Elytron_Security.html#Custom_Components

Force ASP .Net Web API to block HTTP requests with Error (403)

I would like to configure my project to not allow http requests with the following restrictions:
It must be a global restriction for all APIs (via web.config, script in the installer, etc.)
It must be hard coded(not pressing "Require SSL" on the APP in the IIS)
No "redirect"- just return error (403)
my ideal option would be to configure "Require SSL" by a script which runs in the installer.
This can be accomplished by writing a simple ActionFilter that inspects the request and responds when the scheme is not set to ssl. A very minimal implementation may look something like:
public class RequireHttpsAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps)
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
}
}
To make this apply everywhere, you'll likely want to register it as a global filter in the WebAPI configuration when your application is bootstrapping. That would look something like:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new RequireHttpsAttribute());
// ... More configuration ...
}
}
If you search the web a bit, you can find many examples of similar filters with more robust logic that may better meet your needs.

Hystrix getting access to the current execution state within fallback

I successfully configured spring-cloud (via spring-cloud-starter-hystrix) to wrap a call to a service.
This all works fine and looks like the following:
#Component
public class MyService {
#HystrixCommand(fallbackMethod = "fallback")
public void longRunning() {
// this could fail
}
public void fallback() {
// fallback code
}
}
My question now is, I would like to log some statistics about the execution error in longRunning()
Trying to access HystrixRequestLog.getCurrentRequest() within the fallback method throws
java.lang.IllegalStateException: HystrixRequestContext.initializeContext() must be called at the beginning of each request before RequestVariable functionality can be used.
I am looking for a simple way to log the exception of longRunning if the fallback is called.
testing with v1.0.0.RC2
To see a stack trace you can just enable DEBUG logging in com.netflix.hystrix.
As far as I can tell, to use the HystrixRequestContext the caller of MyService has to call HystrixRequestContext.initializeContext() before using the service. That sucks, so if anyone has a better idea, I'm interested.
Starting from Javanica v1.4.21, it allows fallback method to have an argument of Throwable type for accessing the command execution exception like so:
public void fallback(Throwable e) {
// fallback code
LOGGER.error(e.getMessage());
}
To get this feature, your build config needs to override the older version of Javanica pulled in by Spring Cloud.

Map SignalR Hub after using MEF to load plugin

I'm trying to have signalR hub as part of a plugin using MEF. But after calling ImportMany on a List<> object and then adding the catalog/container/ComposeParts part in the Application_Start() method of the Global.asax file, all I get is :
Uncaught TypeError: Cannot read property 'server' of undefined.
I've got no clue if the problem comes from my interface, the plugin, the global.asax file, or the javascript.
The interface:
public interface IPlugin
{
}
the plugin:
[Export(typeof(IPlugin))]
[HubName("testHub")]
public class TestHub : Hub, IPlugin
{
public string Message()
{
return "Hello World!";
}
}
in the Global.asax file:
[ImportMany(typeof (IPlugin))]
private IEnumerable<IPlugin> _plugins { get; set; }
protected void Application_Start()
{
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(#"./Plugins"));
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
RouteTable.Routes.MapHubs();
//log4net
log4net.Config.XmlConfigurator.Configure();
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
and finally the javascript:
$(document).ready(function () {
$.connection.hub.url = 'http://127.0.0.1/signalr/';
var proxy = $.connection.testHub;
$.connection.hub.start({ transport: ['webSockets', 'serverSentEvents', 'longPolling'] })
.done(function () {
proxy.invoke('Message').done(function(res) {
alert(res);
});
})
.fail(function () { alert("Could not Connect!"); });
});
the only information I've found was this post but I could not make it work. everything works fine when I add the reference manually, but when I have a look at "signalr/hubs" after loading the plugin, then there is not reference to my hub's method.
Thanks a lot for your help.
Your problem is that SignalR caches the generated "signalr/hubs" proxy script the first time it is requested. SignalR provides the cached script in response every subsequent request to "signalr/hubs".
SignalR not only caches the script itself, but it also caches the collection of Hubs it finds at the start of the process.
You can work around the cached proxy script issue by simply not using the proxy script, but that still won't enable you to actually connect to Hubs defined in assemblies that are loaded after the process starts.
If you want to be able to connect to such Hubs, you will need to implement your own IHubDescriptorProvider that is aware of Hubs defined in plugins loaded at runtime.
You can register your provider with SignalR's DependencyResolver which can be passed into SignalR via the Resolver property of the HubConfiguration object you pass into MapSignalR.
That said, it would probably be easier to restart the app pool/server process whenever a plugin is added to the "./Plugins" directory.

GWT rpc failing - base url not what I expected

I am trying to become familiar with using the GWT api to create web based applications. I have been following some tutorials on GWT and have not yet been able to make an RPC call. Looking at the problem with a broad scope, my goals are to make a server call to run a series of database tests that I know work (ive tested this code).
---EDIT---
I think that the problem here is that the resource is being looked for here:
/MatesWeb/org.matesweb.Main/peopleService
when I think it should be looked for here:
/MatesWeb/peopleService
---END_EDIT---
Here is the info and code I feel is relevant:
-using netbeans
-error that I am getting is "/MatesWeb/org.matesweb.Main/PeopleService - description - The requested resource is not available."
-GWT.getModuleBaseURL() returns: :8080/MatesWeb/org.matesweb.Main/
-URL in browser is: :8080/MatesWeb/
from web.xml file
<servlet>
<servlet-name>peopleService</servlet-name>
<servlet-class>org.matesweb.server.PeopleServiceImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>peopleService</servlet-name>
<url-pattern>/peopleService</url-pattern>
</servlet-mapping>
From PeopleService Service
package org.matesweb.client;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
#RemoteServiceRelativePath("PeopleService")
public interface PeopleService extends RemoteService {
String[] saveGetPerson(String[] persInfo);
int runTests();
}
From PeopleServiceImpl
package org.matesweb.server;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import org.matesweb.client.PeopleService;
import org.matesweb.server.tests.DbTest;
class PeopleServiceImpl extends RemoteServiceServlet implements PeopleService {
#Override
public String[] saveGetPerson(String[] persInfo) {
throw new UnsupportedOperationException("Not supported yet.");
}
#Override
public int runTests()
{
int retInt;
DbTest dbTest = new DbTest();
retInt = dbTest.runTests();
return retInt;
}
}
From PeopleServiceAsync
package org.matesweb.client;
import com.google.gwt.user.client.rpc.AsyncCallback;
public interface PeopleServiceAsync
{
void saveGetPerson(String[] persInfo, AsyncCallback<String[]> persInformation);
void runTests(AsyncCallback<Integer> retInt);
}
Any idea of whats going on here?
Cheers,
Nick
#RemoteServiceRelativePath("PeopleService")
The #RemoteServiceRelativePath annotation is used to decide what url to hit. This path to the server is relative to the compiled module itself - the gwt app loaded from the path /MatesWeb/org.matesweb.Main/, so the service is being sought out at /MatesWeb/org.matesweb.Main/PeopleService. I assume this means you have an html file in the MatesWeb/ directory (probably the .war file is called MatesWeb?), and inside of there exists the compiled app in org.matesweb.Main/, including the initial JS file, org.matesweb.Main.nocache.js.
If you want to tell the service to be found at /MatesWeb/peopleService, you have two options. The first is to modify the annotation to back up a directory, something like this:
#RemoteServiceRelativePath("../peopleService")
Using .., I indicate the parent directory, and I also changed the case of the path part 'peopleService' - this may or may not matter. A second option is to set the url programmatically:
PeopleServiceAsync service = GWT.create(PeopleService.class);
((ServiceDefTarget)service).setServiceEntryPoint("/MatesWeb/peopleService");
As referenced in the #RemoteServiceRelativePath javadocs http://google-web-toolkit.googlecode.com/svn/javadoc/latest/com/google/gwt/user/client/rpc/RemoteServiceRelativePath.html.
If, instead, you want to leave the client as is and tell the server that this service should be at the path the client expects, you can modify the web.xml to make the servlet available at the path that the client is currently expecting to find it:
<servlet-mapping>
<servlet-name>peopleService</servlet-name>
<url-pattern>/MatesWeb/org.matesweb.Main/PeopleService</url-pattern>
</servlet-mapping>
Note again that I've changed the case - it may not matter, but I generally like to be consistent.
First hunch is PeopleService must be peopleService in the #RemoteServiceRelativePath . Please use firebug to monitor your rpc requests. You can observe and verify request url issues like these easily.
Update your URL pattern in web.xml as in here
<url-pattern>/org.matesweb.Main/greet</url-pattern>