Initial pass of a keycloak filter; it compiles, but hasn't been loaded
into moqui yet.
Showing
3 changed files
with
157 additions
and
0 deletions
build.gradle
0 → 100644
1 | /* | ||
2 | * This software is in the public domain under CC0 1.0 Universal plus a | ||
3 | * Grant of Patent License. | ||
4 | * | ||
5 | * To the extent possible under law, the author(s) have dedicated all | ||
6 | * copyright and related and neighboring rights to this software to the | ||
7 | * public domain worldwide. This software is distributed without any | ||
8 | * warranty. | ||
9 | * | ||
10 | * You should have received a copy of the CC0 Public Domain Dedication | ||
11 | * along with this software (see the LICENSE.md file). If not, see | ||
12 | * <http://creativecommons.org/publicdomain/zero/1.0/>. | ||
13 | */ | ||
14 | |||
15 | apply plugin: 'groovy' | ||
16 | apply plugin: 'java-library' | ||
17 | |||
18 | sourceCompatibility = '1.11' | ||
19 | targetCompatibility = '1.11' | ||
20 | def moquiDir = projectDir.parentFile.parentFile.parentFile | ||
21 | def frameworkDir = file(moquiDir.absolutePath + '/framework') | ||
22 | def componentNode = parseComponent(project) | ||
23 | version = componentNode.'@version' | ||
24 | |||
25 | // maybe in the future: repositories { mavenCentral() } | ||
26 | repositories { | ||
27 | flatDir name: 'localLib', dirs: frameworkDir.absolutePath + '/lib' | ||
28 | flatDir name: 'librepo', dirs: projectDir.absolutePath + '/librepo' | ||
29 | mavenCentral() | ||
30 | } | ||
31 | |||
32 | // Log4J has annotation processors, disable to avoid warning | ||
33 | tasks.withType(JavaCompile) { options.compilerArgs << "-proc:none" } | ||
34 | tasks.withType(GroovyCompile) { options.compilerArgs << "-proc:none" } | ||
35 | |||
36 | dependencies { | ||
37 | implementation project(':framework') | ||
38 | implementation 'org.keycloak:keycloak-servlet-filter-adapter:15.0.2' | ||
39 | } | ||
40 | |||
41 | task cleanLib(type: Delete) { delete fileTree(dir: projectDir.absolutePath+'/lib', include: '*') } | ||
42 | |||
43 | // by default the Java plugin runs test on build, change to not do that (only run test if explicit task) | ||
44 | // no longer workds as of gradle 4.8 or possibly earlier, use clear() instead: check.dependsOn.remove(test) | ||
45 | check.dependsOn.clear() | ||
46 | |||
47 | jar { | ||
48 | destinationDir = file(projectDir.absolutePath + '/lib') | ||
49 | baseName = componentNode.'@name' | ||
50 | } | ||
51 | |||
52 | task copyDependencies { doLast { | ||
53 | copy { from (configurations.runtimeClasspath - project(':framework').configurations.runtimeClasspath - project(':framework').jar.archivePath) | ||
54 | into file(projectDir.absolutePath + '/lib') } | ||
55 | } } | ||
56 | copyDependencies.dependsOn cleanLib | ||
57 | jar.dependsOn copyDependencies | ||
58 |
component.xml
0 → 100644
1 | package org.moqui.keycloak; | ||
2 | |||
3 | import javax.servlet.Filter; | ||
4 | import javax.servlet.FilterChain; | ||
5 | import javax.servlet.FilterConfig; | ||
6 | import javax.servlet.ServletException; | ||
7 | import javax.servlet.ServletRequest; | ||
8 | import javax.servlet.ServletResponse; | ||
9 | import javax.servlet.http.HttpServletRequest; | ||
10 | import javax.servlet.http.HttpServletRequestWrapper; | ||
11 | import javax.servlet.http.HttpServletResponse; | ||
12 | import javax.servlet.http.HttpSession; | ||
13 | |||
14 | public class MoquiKeycloakSecurityFilter implements Filter { | ||
15 | |||
16 | @Override | ||
17 | public void init(FilterConfig config) throws ServletException { | ||
18 | } | ||
19 | |||
20 | @Override | ||
21 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { | ||
22 | if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) { | ||
23 | doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain); | ||
24 | } else { | ||
25 | chain.doFilter(request, response); | ||
26 | } | ||
27 | } | ||
28 | |||
29 | public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { | ||
30 | OIDCServletHttpFacade facade = new OIDCServletHttpFacade(request, response) | ||
31 | KeycloakDeployment keycloakDeployment = getKeycloakDeployment() | ||
32 | OIDCFilterSessionStore tokenStore = new OIDCFilterSessionStore(request, facade, 100000, keycloakDeployment, idMapper) | ||
33 | // TODO: Look at the PolicyEnforcer stuff in keycloak, perhaps hook into that for moqui | ||
34 | // if the thing being called doesn't require AUTH, then the enforcer should return AUTHENTICATED? | ||
35 | FilterRequestAuthenticator authenticator = new FilterRequestAuthenticator(keycloakDeployment, tokenStore, facade, request, 8443) | ||
36 | AuthOutcome outcome = authenticator.authenticate() | ||
37 | if (outcome == AuthOutcome.AUTHENTICATED) { | ||
38 | if (facade.isEnded()) { | ||
39 | return; | ||
40 | } | ||
41 | AuthenticatedActionsHandler actions = new AuthenticatedActionsHandler(keycloakDeployment, facade); | ||
42 | if (actions.handledRequest()) { | ||
43 | return; | ||
44 | } else { | ||
45 | HttpServletRequestWrapper wrapper = tokenStore.buildWrapper(); | ||
46 | postKeycloakFilter(wrapper, response, chain); | ||
47 | return; | ||
48 | } | ||
49 | } | ||
50 | AuthChallenge challenge = authenticator.getChallenge(); | ||
51 | if (challenge != null) { | ||
52 | if (request.getRequestURI().equals('/Login')) { | ||
53 | challenge.challenge(facade); | ||
54 | return; | ||
55 | } | ||
56 | if (request.getMethod().equals('GET')) { | ||
57 | challenge.challenge(facade); | ||
58 | return; | ||
59 | } | ||
60 | // TODO | ||
61 | //challenge.challenge(facade); | ||
62 | //return; | ||
63 | } | ||
64 | // TODO | ||
65 | // sendError(403) | ||
66 | postKeycloakFilter(request, response, chain); | ||
67 | } | ||
68 | |||
69 | protected void postKeycloakFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { | ||
70 | // Can also look at the session if needed | ||
71 | KeycloakSecurityContext ksc = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName()); | ||
72 | logger.debug("doFilter(" + ksc + ")"); | ||
73 | //showKeycloakSecurityContext(ksc); | ||
74 | //importKeycloakSecurityContext(ksc); | ||
75 | String username = null; | ||
76 | if (ksc != null) { | ||
77 | ExecutionContext ec = Moqui.getExecutionContext(); | ||
78 | //ec.user.pushUser('keycloak-api'); | ||
79 | try { | ||
80 | // FIXME: This is bad, I don't know how to force a login without a password. | ||
81 | ec.getUser().loginUser('keycloak-api', 'moqui'); | ||
82 | Map<String, Object> result = ec.service.sync().name("bf.auth.KeycloakServices.import#KeycloakUser").parameters([ksc: ksc]).call(); | ||
83 | logger.debug('result=' + result) | ||
84 | username = result?.userAccount?.username | ||
85 | request.setAttribute('moqui.request.authenticated', 'true') | ||
86 | } finally { | ||
87 | //ec.user.popUser(); | ||
88 | } | ||
89 | } | ||
90 | shiroRunAs(username, request.getSession(), { chain.doFilter(request, response) }); | ||
91 | } | ||
92 | |||
93 | |||
94 | } |
-
Please register or sign in to post a comment