New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Smart Configuration #186
Smart Configuration #186
Conversation
Codecov Report
@@ Coverage Diff @@
## master #186 +/- ##
============================================
- Coverage 80.82% 79.04% -1.78%
- Complexity 1482 1497 +15
============================================
Files 161 167 +6
Lines 3812 3937 +125
Branches 497 516 +19
============================================
+ Hits 3081 3112 +31
- Misses 508 600 +92
- Partials 223 225 +2
Continue to review full report at Codecov.
|
omod/src/main/resources/config.xml
Outdated
<filter-mapping> | ||
<filter-name>clientAuthenticationFilter</filter-name> | ||
<url-pattern>/keycloak/*</url-pattern> | ||
<url-pattern>/protected/*</url-pattern> | ||
</filter-mapping> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So what URLs do we actually want to protect?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ibacher I am not very sure about it that's why I left it unchanged
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The url-pattern
determines which URLs this filter is run against. It should probably look very similar to the AuthenticationFilter
, since they're ultimately protecting the same thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't you think that it'll be different than that in AuthenticationFilter
because earlier we did not have any client (SMART App), we only had users
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, because we're still protecting the same resources either way. That's what's important here.
@Override | ||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { | ||
super.doFilter(req, res, chain); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ibacher Could you give some idea on what all should go in here as most of the code that we might need is already present in the super function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So what we need to do is to figure out the user name. From here, it looks like we can do that by getting the appropriate request attribute.
Then, we need to login the user. This can be done by running Context.becomeUser()
with the user id (which we can hopefully obtain from the account.
5de2332
to
fb9911f
Compare
omod/src/main/resources/config.xml
Outdated
@@ -67,7 +67,11 @@ | |||
<filter-mapping> | |||
<filter-name>clientAuthenticationFilter</filter-name> | |||
<url-pattern>/keycloak/*</url-pattern> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You definitely don't need this URL pattern. A URL pattern is just matched against the incoming URL without the host (http://localhost) or context (/openmrs), and OpenMRS doesn't have anything that responds to /keycloak.
4968b9d
to
29d7ebe
Compare
if (!httpRequest.getAuthType().contains("BASIC")) { | ||
|
||
super.doFilter(req, res, chain); | ||
if (httpRequest.getRequestedSessionId() != null && !httpRequest.isRequestedSessionIdValid()) { | ||
Context.logout(); | ||
} | ||
if (httpRequest.getAttribute(KeycloakAccount.class.getName()) != null) { | ||
OidcKeycloakAccount account = (OidcKeycloakAccount) httpRequest | ||
.getAttribute(KeycloakAccount.class.getName()); | ||
Context.becomeUser(account.getPrincipal().getName()); | ||
} else { | ||
if (!res.isCommitted()) { | ||
HttpServletResponse httpResponse = (HttpServletResponse) res; | ||
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Not authenticated"); | ||
return; | ||
} | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ibacher This gives an NPE but don't know why it makes sense to me. The super function will always commit to response thats why I thought to write something like this.
Error log
SEVERE: Servlet.service() for servlet [openmrs] in context with path [/openmrs] threw exception
java.lang.NullPointerException
at org.openmrs.module.fhir2.web.filter.ClientAuthenticationFilter.doFilter(ClientAuthenticationFilter.java:47)
at org.openmrs.module.web.filter.ModuleFilterChain.doFilter(ModuleFilterChain.java:71)
at org.openmrs.module.referenceapplication.filter.RequireLoginLocationFilter.doFilter(RequireLoginLocationFilter.java:93)
at org.openmrs.module.web.filter.ModuleFilterChain.doFilter(ModuleFilterChain.java:71)
at org.openmrs.module.web.filter.ModuleFilter.doFilter(ModuleFilter.java:57)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
The basic auth also doesn't works, neither for the last commit nor this one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what part of the code this is referring to exactly, because I think the line numbers have shifted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@VaishSiddharth Thanks for your work on this! These changes are looking really goood! I've made a few comments, but this feels like it's starting to come together.
@Data | ||
public class SmartConformance { | ||
|
||
private String authorization_endpoint; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't have to be dealt with straight-away, but these should be camelCase, i.e., authorizationEndpoint
rather than authorization_endpoint
. We can then translate them to the appropriate name using an annotation, i.e.,
@JsonProperty("authorization_endpoint")
private String authorizationEndpoint;
@Override | ||
public void destroy() { | ||
super.destroy(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can probably get rid of this method.
|
||
if (requestURI.contains("/.well-known")) { | ||
replacement = "/ms/fhir2SmartConfig"; | ||
} | ||
String newURI = requestURI.replace(prefix, replacement); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be a new line before here.
} else { | ||
((HttpServletResponse) res).sendError(HttpServletResponse.SC_NOT_FOUND); | ||
return; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually think we should have a new line here
@@ -53,13 +53,17 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) | |||
} else if (R4.equals(fhirVersionCase)) { | |||
prefix += "/" + fhirVersion; | |||
replacement = "/ms/fhir2Servlet"; | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new line should not exist
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { | ||
if (req instanceof HttpServletRequest) { | ||
HttpServletRequest httpRequest = (HttpServletRequest) req; | ||
if (!httpRequest.getAuthType().contains("BASIC")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be:
if (httpRequest.getAuthType() != null && !httpRequest.getAuthType().equals(HttpServletRequest.BASIC_AUTH))
ObjectMapper objectMapper = new ObjectMapper(); | ||
|
||
SmartConformance smartConformance = new SmartConformance(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason these can't be class objects? I.e., so we only have one objectMapper
and one smartConformance
per servlet, rather than creating new ones for every request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ibacher Not sure about what should be done here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh... I just meant turn them into class properties:
class Fhir2SmartConfig extends HttpServlet {
private static final ObjectMapper objectMapper = new ObjectManager();
private final SmartConformance smartConformance;
public Fhir2SmartConfig() {
this.smartConformance = new SmartConformance();
// set properties here
}
public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
PrintWriter out = res.getWriter();
res.setContentType("application/json");
res.setCharacterEncoding("UTF-8");
res.setStatus(200);
objectMapper.writeValue(out, smartConformance);
// why do we need these next two lines?
out.print(objectMapper);
out.flush();
}
}
OidcKeycloakAccount account = (OidcKeycloakAccount) httpRequest | ||
.getAttribute(KeycloakAccount.class.getName()); | ||
|
||
String userName = account.getKeycloakSecurityContext().getToken().getSubject(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bacher This still give the internal keycloak user id
This solves the error.
String userName = account.getKeycloakSecurityContext().getToken().getPreferredUsername();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, use that.
if (httpRequest.getContextPath() != null) { | ||
newRequestUri.append(httpRequest.getContextPath()); | ||
} | ||
|
||
if (httpRequest.getServletPath() != null) { | ||
newRequestUri.append(httpRequest.getServletPath()); | ||
} | ||
|
||
if (httpRequest.getPathInfo() != null) { | ||
newRequestUri.append(httpRequest.getPathInfo()); | ||
} | ||
|
||
newRequestUri.append('?').append(newQueryString); | ||
|
||
req.getRequestDispatcher(newRequestUri.toString()).forward(req, res); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ibacher Don't you think that we still need the ForwardingFilter
to execute?
Also there is a mistake in the url formation it adds /openmrs/openmrs two times
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't intended to override the ForwardingFilter, only to remove the access_token
from the query before it hits HAPI. Note that this "forwarding" is really just the way to internally change the URL without having to worry about too many details.
Also there is a mistake in the url formation it adds /openmrs/openmrs two times
That clearly needs to be fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, we can't support config.xml
in a non-OMOD submodule. Which does mean that we're going to need another means of loading the SMART filter. I'll have to give that some thought.
omod/pom.xml
Outdated
@@ -38,7 +38,7 @@ | |||
</dependency> | |||
<dependency> | |||
<groupId>${project.parent.groupId}</groupId> | |||
<artifactId>smart</artifactId> | |||
<artifactId>smartSupport</artifactId> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably call this smart-support
@@ -9,8 +9,8 @@ | |||
graphic logo is a trademark of OpenMRS Inc. | |||
--> | |||
<dataset> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why this is part of this commit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ibacher Earlier I had changed this so that the tests pass for openmrsPlaformVersion 2.3.0. So I reverted it back to this after creating a new sub module
@@ -310,7 +310,7 @@ public void searchForPatients_shouldSearchForPatientsByBirthDateWithLowerBound() | |||
|
|||
assertThat(results, notNullValue()); | |||
assertThat(get(results), not(empty())); | |||
assertThat(get(results), hasSize(equalTo(1))); | |||
assertThat(get(results), hasSize(greaterThan(1))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why this is part of this commit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ibacher Earlier I had changed this so that the tests pass for openmrsPlaformVersion 2.3.0. So I reverted it back to this after creating a new sub module
pom.xml
Outdated
@@ -657,7 +657,7 @@ | |||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |||
<project.build.outputEncoding>UTF-8</project.build.outputEncoding> | |||
<lombokVersion>1.18.10</lombokVersion> | |||
<openmrsPlatformVersion>2.3.0</openmrsPlatformVersion> | |||
<openmrsPlatformVersion>2.0.2</openmrsPlatformVersion> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be 2.0.5. Also, please ensure your IDE is configured to indent XML files with tabs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok will make this change
cd88de1
to
ab63c34
Compare
catch (Exception ignored) { | ||
HttpServletResponse httpResponse = (HttpServletResponse) response; | ||
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Not authenticated"); | ||
if (!response.isCommitted()) { | ||
HttpServletResponse httpResponse = (HttpServletResponse) response; | ||
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Not authenticated"); | ||
} | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ibacher The only error that is left now is this, even on giving the correct password and username for basic auth the catch block is executed with invalid credentials error
I guess that there is some problem with the authentication scheme I mean either its not set or it is changed because of the keycloak filter. I can say this because I copy pasted the same AuthenticationFilter
code to the master branch and tried it with the same request and it worked perfectly fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@VaishSiddharth This change should be made outside of this PR, since it's outside of the scope of the SMART stuff. I'm aware that's not really the question, but to see why this is failing, this really needs to be stepped through with a debugger.
|
||
if (!(credentials instanceof SmartTokenCredentials)) { | ||
throw new ContextAuthenticationException("Invalid credentials"); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ibacher I figured out that the error is because of this part. I tried changing it to
if (!(credentials instanceof SmartTokenCredentials)) {
return new BasicAuthenticated(dao.getUserByUserName(credentials.getClientName()),
UsernamePasswordCredentials.SCHEME);
}
But it didn't work.
Could you help me with this?
Closing this as we are moving this support to a new module. |
Description of what I changed
Issue I worked on
see https://issues.openmrs.org/browse/FM2-
Checklist: I completed these to help reviewers :)
My IDE is configured to follow the code style of this project.
No? Unsure? -> configure your IDE, format the code and add the changes with
git add . && git commit --amend
I have added tests to cover my changes. (If you refactored
existing code that was well tested you do not have to add tests)
No? -> write tests and add them to this commit
git add . && git commit --amend
I ran
mvn clean package
right before creating this pull request andadded all formatting changes to my commit.
No? -> execute above command
All new and existing tests passed.
No? -> figure out why and add the fix to your commit. It is your responsibility to make sure your code works.
My pull request is based on the latest changes of the master branch.
No? Unsure? -> execute command
git pull --rebase upstream master