2065117c by Adam Heath

Initial pass of a keycloak filter; it compiles, but hasn't been loaded

into moqui yet.
1 parent f12184d3
/*
* This software is in the public domain under CC0 1.0 Universal plus a
* Grant of Patent License.
*
* To the extent possible under law, the author(s) have dedicated all
* copyright and related and neighboring rights to this software to the
* public domain worldwide. This software is distributed without any
* warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication
* along with this software (see the LICENSE.md file). If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
apply plugin: 'groovy'
apply plugin: 'java-library'
sourceCompatibility = '1.11'
targetCompatibility = '1.11'
def moquiDir = projectDir.parentFile.parentFile.parentFile
def frameworkDir = file(moquiDir.absolutePath + '/framework')
def componentNode = parseComponent(project)
version = componentNode.'@version'
// maybe in the future: repositories { mavenCentral() }
repositories {
flatDir name: 'localLib', dirs: frameworkDir.absolutePath + '/lib'
flatDir name: 'librepo', dirs: projectDir.absolutePath + '/librepo'
mavenCentral()
}
// Log4J has annotation processors, disable to avoid warning
tasks.withType(JavaCompile) { options.compilerArgs << "-proc:none" }
tasks.withType(GroovyCompile) { options.compilerArgs << "-proc:none" }
dependencies {
implementation project(':framework')
implementation 'org.keycloak:keycloak-servlet-filter-adapter:15.0.2'
}
task cleanLib(type: Delete) { delete fileTree(dir: projectDir.absolutePath+'/lib', include: '*') }
// by default the Java plugin runs test on build, change to not do that (only run test if explicit task)
// no longer workds as of gradle 4.8 or possibly earlier, use clear() instead: check.dependsOn.remove(test)
check.dependsOn.clear()
jar {
destinationDir = file(projectDir.absolutePath + '/lib')
baseName = componentNode.'@name'
}
task copyDependencies { doLast {
copy { from (configurations.runtimeClasspath - project(':framework').configurations.runtimeClasspath - project(':framework').jar.archivePath)
into file(projectDir.absolutePath + '/lib') }
} }
copyDependencies.dependsOn cleanLib
jar.dependsOn copyDependencies
<?xml version="1.0" encoding="UTF-8"?>
<component xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/moqui-conf-3.xsd"
name="moqui-keycloak" version="0.0.0">
<depends-on name="mantle-udm" version="2.2.0"/>
</component>
package org.moqui.keycloak;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class MoquiKeycloakSecurityFilter implements Filter {
@Override
public void init(FilterConfig config) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
} else {
chain.doFilter(request, response);
}
}
public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
OIDCServletHttpFacade facade = new OIDCServletHttpFacade(request, response)
KeycloakDeployment keycloakDeployment = getKeycloakDeployment()
OIDCFilterSessionStore tokenStore = new OIDCFilterSessionStore(request, facade, 100000, keycloakDeployment, idMapper)
// TODO: Look at the PolicyEnforcer stuff in keycloak, perhaps hook into that for moqui
// if the thing being called doesn't require AUTH, then the enforcer should return AUTHENTICATED?
FilterRequestAuthenticator authenticator = new FilterRequestAuthenticator(keycloakDeployment, tokenStore, facade, request, 8443)
AuthOutcome outcome = authenticator.authenticate()
if (outcome == AuthOutcome.AUTHENTICATED) {
if (facade.isEnded()) {
return;
}
AuthenticatedActionsHandler actions = new AuthenticatedActionsHandler(keycloakDeployment, facade);
if (actions.handledRequest()) {
return;
} else {
HttpServletRequestWrapper wrapper = tokenStore.buildWrapper();
postKeycloakFilter(wrapper, response, chain);
return;
}
}
AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) {
if (request.getRequestURI().equals('/Login')) {
challenge.challenge(facade);
return;
}
if (request.getMethod().equals('GET')) {
challenge.challenge(facade);
return;
}
// TODO
//challenge.challenge(facade);
//return;
}
// TODO
// sendError(403)
postKeycloakFilter(request, response, chain);
}
protected void postKeycloakFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// Can also look at the session if needed
KeycloakSecurityContext ksc = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
logger.debug("doFilter(" + ksc + ")");
//showKeycloakSecurityContext(ksc);
//importKeycloakSecurityContext(ksc);
String username = null;
if (ksc != null) {
ExecutionContext ec = Moqui.getExecutionContext();
//ec.user.pushUser('keycloak-api');
try {
// FIXME: This is bad, I don't know how to force a login without a password.
ec.getUser().loginUser('keycloak-api', 'moqui');
Map<String, Object> result = ec.service.sync().name("bf.auth.KeycloakServices.import#KeycloakUser").parameters([ksc: ksc]).call();
logger.debug('result=' + result)
username = result?.userAccount?.username
request.setAttribute('moqui.request.authenticated', 'true')
} finally {
//ec.user.popUser();
}
}
shiroRunAs(username, request.getSession(), { chain.doFilter(request, response) });
}
}