Skip to content
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

[BUG] MirthConnect Version 4.5.0 - Java 17 - Web Service Sender - button "Get Operations" fails if authentication is needed #6169

Open
javandre opened this issue Apr 17, 2024 · 13 comments
Labels
bug Something isn't working

Comments

@javandre
Copy link

javandre commented Apr 17, 2024

Describe the bug
In a "Web Service Sender" destination the button "Get Operations" produces an error when "Authentication" is set to "Yes" and the WSDL server requires authentication. Bug is consistently reproducible.

To Reproduce

  1. Create new channel
  2. Set destination to "Web Service Sender"
  3. Enable Authentication, fill in username and password
  4. Enter URL to WSDL server (ensure that this WSDL server needs an authentication)
  5. Click "Get Operations"

Expected behavior
Get operations from WSDL Server without error.

Actual behavior
Java-Error occurs (screenshot 1 and 2), operations are not loaded.

Screenshots
Screenshot 1: Java Error Window
image

Screenshot 2: Error in mirth.log file
image

Environment:

  • OS: Linux (Ubuntu 22.04), Windows 11
  • Java: OpenJDK 17
  • Connect Version 4.5.0
    • the Java9+ entries in the vmoptions file are included

Workaround
Fill in the needed WSDL operations manually.

Additional context
Hint: The destination works correctly as a WebServiceSender if you fill in the operations manually. Only clicking the button "Get Operations" fails.

@javandre javandre added the bug Something isn't working label Apr 17, 2024
@HappyHampi
Copy link

Hi @javandre
Thanks for posting this Bug.

@jonbartels
Copy link
Contributor

image

and

the Java9+ entries in the vmoptions file are included

I think you need additional Java9 entries for the WebService sender to work.

This is still a bug in MC, since the suggested options should support the software that NextGen publishes. I think a workaround would be to add this to your VM options file:
--add-opens=java.base/java.util.concurrent=ALL-UNNAMED

@HappyHampi
Copy link

Hi @jonbartels
We tried this today without success.

image

@jonbartels
Copy link
Contributor

@hanspeterbrun read the latest error message. It is still a modules error but it is a DIFFERENT module.

You need additional opens entries

@javandre
Copy link
Author

Hi @jonbartels

Thanks for your comments. We added this java.io entry, too. After this we got no more errors in mirth.log, but a HTTP 401 error as response. I think that no username/password was transmitted with the request, because the destination could not read the input textfields.

@jonbartels
Copy link
Contributor

Are you using hardcoded credentials or mapped values? "Test Connection" usually does not work with mapped values.

Can you change the URL to a server you control and see what it sends? You should see the appropriate authn headers. The request won't work but you should be able to see what Mirth is transmitting.

@javandre
Copy link
Author

I'm a little bit confused, I think we are talking about the button "Get Operations" (not "Test Connection")...

Yes, I'm using hardcoded credentials and I'm sure there are correct (tested with SoapUI).

Unfortunately I don't have a SOAP server under my control at the moment. I'll try to check this later and then comment here.

@pacmano1
Copy link
Collaborator

You don't need a soap server.

e.g. if you have node installed:

npx http-echo-erver // starts a dumb http listener on port 3000 and echos request to console

@pacmano1
Copy link
Collaborator

pacmano1 commented Apr 18, 2024

Actaully tried this - and it does not send authorization.

# npx http-echo-server
[server] event: listening (port: 3000)
[server] event: connection (socket#1)
[socket#1] event: resume
[socket#1] event: readable
[socket#1] event: end
[socket#1] event: prefinish
[socket#1] event: finish
[socket#1] event: close
[server] event: connection (socket#2)
[socket#2] event: resume
[socket#2] event: data
--> GET / HTTP/1.1
--> Host: localhost:3000
--> Connection: Keep-Alive
--> User-Agent: Apache-HttpClient/4.5.13 (Java/17.0.10)
--> Accept-Encoding: gzip,deflate
--> 
--> 
[socket#2] event: prefinish
[socket#2] event: finish
[socket#2] event: readable
[socket#2] event: end
[socket#2] event: close

@pacmano1
Copy link
Collaborator

Workaround of course is just the grab the WSDL and host it anywhere you can in fact access.

@jonbartels
Copy link
Contributor

@pacmano1 seems to have reproduced the issue.

@javandre you should edit your bug report to focus ONLY on the WSDL auth issue. The opens issues were unrelated.

The relevant code that generates the auth error is:

getServlet(WebServiceConnectorServletInterface.class, "Getting operations...", "Error caching WSDL. Please check the WSDL URL and authentication settings.\n\n", handler).cacheWsdlFromUrl(getChannelId(), getChannelName(), (WebServiceDispatcherProperties) getFilledProperties());

This generates the credentials in the URL:

public URI getURIWithCredentials(URI wsdlUrl, String username, String password) throws URISyntaxException {

This generates the credentials in the format https://user:pass@domain/.... . This condition is not visible in @pacmano1 test case. It also seems plausible that this format is not accepted by many servers. @pacmano1 you might need to re-try your test case with nc or a different tool to see if the credentials are being transmitted correctly.

@javandre what authentication mechanism is the SOAP server expecting you to use for the WSDL? HTTP Basic? Oauth? something else?

Using a browser or curl can you retrieve the WSDL with the https://user:pass@domain/.... format?

@jluengo-meditecs
Copy link

I think that I have found the problem. In this commit prior to the version 4.5.0, the existing SoapUI library that handled WSDL requests (com.eviware.soapui.impl.wsdl.support.wsdl.WsdlLoader) was replaced by the Java native javax.wsdl.xml.WSDLReader one.

The root of the problem seems to be that Mirth Connect is placing the Basic auth credentials from the connector form directly in the URL. This worked for SoapUI library, but I think that it is forbidden on purpose in the native Java implementation at some level. In fact it makes sense, since for example I have just found a URL with credentials in the console of my server instance. This could cause passwords to be leaked for some.

I can think of a few ways to solve this bug in no particular order:

  • Rolling back to the previous library. I don't believe it is an option, as I suppose that if they have migrated to the native Java package it is for a reason.
  • Setting a default java.net.Authenticator. For me this is the best and the intended way of using this library and it even supports more authentication methods. Sadly, it is not very straightforward, as we are not able to pass the credentials directly. The only way I found to do it seems to define it for the entire application.
    My idea is to define a single Authenticator for the Mirth Connect instance with access to a in-memory a pair of user/password for every URL that is being requested. By identifying the requested URL using getRequestingURL() method, we could return the right authentication credentials for each URL.
    Maybe just after returning a pair of credentials once, they might be automatically deleted.
  • Passing a WSDLLocator object. Instead of passing a String, the library supports receiving an object that implements the WSDL download part, offering an InputSource with the XML. Here seems to be an implementation from an Apache abandoned project.
  • Using another library to handle this. This would probably require an adaptation in order to process other generated objects.
  • Downloading the WSDL and processing it entirely. It has the advantage of not depending on a dedicated library. Probably the hardest of all and the most prone to errors.

@jonbartels netcat or similar will not work, since at least the Mirth Connect versions prior to 4.5.0 used the HTTP authentication framework, so they wait for the server to ask back for the password if needed (The client is expecting a 401 error code along with a WWW-Authenticate header before sending the password). A simple way to reproduce this bug could be to create another channel with an HTTP listener and with Basic authentication.
Also, the bug doesn't have to do with the server receiving credentials in other format, as Basic authentication credentials are always sent and received in the same way.
The user:password URL syntax part is totally interpreted by the client to know which credentials it has to use in the server without asking the user or obtaining them in any other way.

I also suggest a simple way to workaround this issue. It is to create another channel with an HTTP listener and an HTTP sender that acts as a proxy for the other channel, requesting the WSDL for it using the appropriate credentials. After using it, it could simply be disabled or removed.

@jluengo-meditecs
Copy link

I just realized another simpler way to solve it without changing much!

We could just simply define a default Authenticator implementation that gets the user:password part of the URL if it exists and then returns it.

I even have just wrote a Rhino script that if executed once will even fix the issue until the server is restarted. It can be put for example in the deploy script of any channel or in the global deploy script.

The following works, but the username and password parsing has to be done better to comply with RFC 1738.

any ":", "@", or "/" must be encoded

var url_userinfo_authenticator = new java.net.Authenticator({
    getPasswordAuthentication: function() {
        var userInfo = null;
        try {
            var url = new java.net.URL(this.getRequestingURL());
            userInfo = url.getUserInfo();
        } catch (e) {
            e.printStackTrace();
        }

        if (userInfo != null) {
            var parts = userInfo.split(":");
            if (parts.length === 2) {
                var username = parts[0];
                var password = parts[1];
                return new java.net.PasswordAuthentication(username, password.toCharArray());
            }
        }

        return null;
    }
});

java.net.Authenticator.setDefault(url_userinfo_authenticator);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

5 participants