Giter VIP home page Giter VIP logo

oauth1-signer-java's Introduction

oauth1-signer-java

Table of Contents

Overview

Zero dependency library for generating a Mastercard API compliant OAuth signature.

Compatibility

Java 11+

References

Versioning and Deprecation Policy

Usage

Prerequisites

Before using this library, you will need to set up a project in the Mastercard Developers Portal.

As part of this set up, you'll receive credentials for your app:

  • A consumer key (displayed on the Mastercard Developer Portal)
  • A private request signing key (matching the public certificate displayed on the Mastercard Developer Portal)

Adding the Library to Your Project

Maven

<dependency>
    <groupId>com.mastercard.developer</groupId>
    <artifactId>oauth1-signer</artifactId>
    <version>${oauth1-signer-version}</version>
</dependency>

Gradle

dependencies {
    implementation "com.mastercard.developer:oauth1-signer:$oauth1SignerVersion"
}

Other Dependency Managers

See: https://search.maven.org/artifact/com.mastercard.developer/oauth1-signer

Loading the Signing Key

A PrivateKey key object can be created by calling the AuthenticationUtils.loadSigningKey method:

PrivateKey signingKey = AuthenticationUtils.loadSigningKey(
                                    "<insert PKCS#12 key file path>", 
                                    "<insert key alias>", 
                                    "<insert key password>");

Creating the OAuth Authorization Header

The method that does all the heavy lifting is OAuth.getAuthorizationHeader. You can call into it directly and as long as you provide the correct parameters, it will return a string that you can add into your request's Authorization header.

String consumerKey = "<insert consumer key>";
URI uri = URI.create("https://sandbox.api.mastercard.com/service");
String method = "POST";
String payload = "Hello world!";
Charset charset = StandardCharsets.UTF_8;
String authHeader = OAuth.getAuthorizationHeader(uri, method, payload, charset, consumerKey, signingKey);

Signing HTTP Client Request Objects

Alternatively, you can use helper classes for some of the commonly used HTTP clients.

These classes, provided in the com.mastercard.developer.signers package, will modify the provided request object in-place and will add the correct Authorization header. Once instantiated with a consumer key and private key, these objects can be reused.

Usage briefly described below, but you can also refer to the test package for examples.

Java HttpsURLConnection

Charset charset = StandardCharsets.UTF_8;
URL url = new URL("https://sandbox.api.mastercard.com/service");
String payload = "{\"foo\":\"bar\"}";

HttpsURLConnection con = (HttpsURLConnection)url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/json; charset=" + charset.name());

HttpsUrlConnectionSigner signer = new HttpsUrlConnectionSigner(charset, consumerKey, signingKey);
signer.sign(con, payload);

Apache HTTP Client 4

String payload = "{\"foo\":\"bar\"}";

HttpClient httpClient = HttpClientBuilder.create().build();
HttpPost httpPost = new HttpPost("https://sandbox.api.mastercard.com/service");
httpPost.setEntity(new StringEntity(payload, ContentType.APPLICATION_JSON));

ApacheHttpClient4Signer signer = new ApacheHttpClient4Signer(consumerKey, signingKey);
signer.sign(httpPost);

OkHttp 3

MediaType JSON = MediaType.parse("application/json; charset=utf-8");
String payload = "{\"foo\":\"bar\"}";

OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(JSON, payload);
Request.Builder request = new Request.Builder()
        .url("https://sandbox.api.mastercard.com/service")
        .post(body);

OkHttpSigner signer = new OkHttpSigner(consumerKey, signingKey);
signer.sign(request);

Integrating with OpenAPI Generator API Client Libraries

OpenAPI Generator generates API client libraries from OpenAPI Specs. It provides generators and library templates for supporting multiple languages and frameworks.

The com.mastercard.developer.interceptors package will provide you with some request interceptor classes you can use when configuring your API client. These classes will take care of adding the correct Authorization header before sending the request.

Library options currently supported for the java generator:

See also:

okhttp-gson

OpenAPI Generator Plugin Configuration
<configuration>
    <inputSpec>${project.basedir}/src/main/resources/openapi-spec.yaml</inputSpec>
    <generatorName>java</generatorName>
    <library>okhttp-gson</library>
    <!-- ... -->
</configuration>
Usage of the OkHttp2OAuth1Interceptor (OpenAPI Generator 3.3.x)
ApiClient client = new ApiClient();
client.setBasePath("https://sandbox.api.mastercard.com");
List<Interceptor> interceptors = client.getHttpClient().interceptors();
interceptors.add(new OkHttp2OAuth1Interceptor(consumerKey, signingKey));
ServiceApi serviceApi = new ServiceApi(client);
// ...
Usage of the OkHttpOAuth1Interceptor (OpenAPI Generator 4+)
ApiClient client = new ApiClient();
client.setBasePath("https://sandbox.api.mastercard.com");
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("https://proxy-url.com", 8866)); // Optional Proxy Configuration
client.setHttpClient(
    client.getHttpClient()
        .newBuilder()
        .proxy(proxy) // Optional proxy
        .addInterceptor(new OkHttpOAuth1Interceptor(consumerKey, signingKey))
        .build()
);
ServiceApi serviceApi = new ServiceApi(client);
// ...

feign

OpenAPI Generator Plugin Configuration
<configuration>
    <inputSpec>${project.basedir}/src/main/resources/openapi-spec.yaml</inputSpec>
    <generatorName>java</generatorName>
    <library>feign</library>
    <!-- ... -->
</configuration>
Usage of the OpenFeignOAuth1Interceptor
ApiClient client = new ApiClient();
client.setBasePath("https://sandbox.api.mastercard.com");
Feign.Builder feignBuilder = client.getFeignBuilder();
ArrayList<RequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(new OpenFeignOAuth1Interceptor(consumerKey, signingKey, client.getBasePath()));
feignBuilder.requestInterceptors(interceptors);
ServiceApi serviceApi = client.buildClient(ServiceApi.class);
// ...

retrofit

OpenAPI Generator Plugin Configuration
<configuration>
    <inputSpec>${project.basedir}/src/main/resources/openapi-spec.yaml</inputSpec>
    <generatorName>java</generatorName>
    <library>retrofit</library>
    <!-- ... -->
</configuration>
Usage of the OkHttp2OAuth1Interceptor
ApiClient client = new ApiClient();
RestAdapter.Builder adapterBuilder = client.getAdapterBuilder();
adapterBuilder.setEndpoint("https://sandbox.api.mastercard.com"); 
List<Interceptor> interceptors = client.getOkClient().interceptors();
interceptors.add(new OkHttp2OAuth1Interceptor(consumerKey, signingKey));
ServiceApi serviceApi = client.createService(ServiceApi.class);
// ...

retrofit2

OpenAPI Generator Plugin Configuration
<configuration>
    <inputSpec>${project.basedir}/src/main/resources/openapi-spec.yaml</inputSpec>
    <generatorName>java</generatorName>
    <library>retrofit2</library>
    <!-- ... -->
</configuration>
Usage of the OkHttpOAuth1Interceptor
ApiClient client = new ApiClient();
Retrofit.Builder adapterBuilder = client.getAdapterBuilder();
adapterBuilder.baseUrl("https://sandbox.api.mastercard.com"); 
OkHttpClient.Builder okBuilder = client.getOkBuilder();
okBuilder.addInterceptor(new OkHttpOAuth1Interceptor(consumerKey, signingKey));
ServiceApi serviceApi = client.createService(ServiceApi.class);
// ...

google-api-client

OpenAPI Generator Plugin Configuration
<configuration>
    <inputSpec>${project.basedir}/src/main/resources/openapi-spec.yaml</inputSpec>
    <generatorName>java</generatorName>
    <library>google-api-client</library>
    <!-- ... -->
</configuration>
Usage of the HttpExecuteOAuth1Interceptor
HttpRequestInitializer initializer = new HttpRequestInitializer() {
    @Override
    public void initialize(HttpRequest request) {
        request.setInterceptor(new HttpExecuteOAuth1Interceptor(consumerKey, signingKey));
    }
};
ApiClient client = new ApiClient("https://sandbox.api.mastercard.com", null, initializer, null);
ServiceApi serviceApi = client.serviceApi();
// ...

oauth1-signer-java's People

Contributors

arankin-irl avatar danny-gallagher avatar dependabot[bot] avatar ech0s7r avatar fileme avatar jaaufauvre avatar karen-avetisyan-mc avatar kkrauth avatar nehasony avatar rfeelin avatar rossphelan avatar shimonar-mc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

oauth1-signer-java's Issues

[REQ] Code Improvement suggestion for unnecessary character replacment in the Util.percentEncode.

Is your feature request related to a problem?

No

Describe the solution you'd like

The java class
src/main/java/com/mastercard/developer/oauth/OAuth.java
line 64 contains the below

// Signature
   String signature = signSignatureBaseString(sbs, signingKey, charset);
   oauthParams.put("oauth_signature", Util.percentEncode(signature, charset));

The method Util.percentEncode tries to replace the below characters
"+", "" and "%7E"
I am aware the java.net.URLEncode.encode does replace "+" with "%2B" and replace space character with "+" character
The "signature" String is already base64 encoded which contains only from character 'A' to '0',.... '9'...'+','/', '='
see Base64 character table
The '+' will be already encoded by the URLEncoder so there character replacement in the Util java class as below
.replace("+", "%20")
.replace("
", "%2A")
.replace("%7E", "~");

Are not needed I think.

Describe alternatives you've considered

Please reconsider to remove the character replacement in the Util class
com.mastercard.developer.oauth.Util line 32 to 34

Additional context

Do you recommend me to create a pull request (PR) to address the optimization issue mentioned above ?

[REQ] Accept a payload of type byte[] in OAuth.getAuthorizationHeader()

The current OAuth.getAuthorizationHeader() method has a payload parameter of type String. In cases where the payload is not available as a String, conversion to a String might be impossible (binary data) or inefficient in memory usage. Providing a overloaded version of OAuth.getAuthorizationHeader() that accepts a byte[] payload would be helpful in these cases.

The existing code also makes an assumption that the character set for all string values passed to OAuth.getAuthorizationHeader() is the same. This may not be a safe assumption in all cases. Allowing payload to be passed a bytes provides a partial workaround.

I would be happy to provide the trivial PR to address this issue if desired.

[REQ] Spring Boot 3 support and file generation with javax imports in oauth1-signer-java

I'm working on a project that uses Mastercard's oauth1-signer-java, and I've encountered an issue related to Spring Boot 3 support and generating files with imports from javax instead of jakarta.

When trying to compile my project with Spring Boot 3, I encounter a series of errors in the logs indicating that the Generated symbol cannot be found in the javax.annotation package. I attach some examples of the errors I am seeing in the logs:

[ERROR] symbol: class Generated
[ERROR] location: package javax.annotation
[ERROR] /route/to/my/proyect/target/generated-sources/openapi/src/gen/java/com/mastercard/api/mastercom/model/TransactionSummary.java:[53,17] error: cannot find symbol

I've done some research and it looks like these errors may be related to incompatibility between Spring Boot 3 and the oauth1-signer-java dependency. I'm wondering if there are any plans to address this incompatibility in future versions of the project.

Any help or suggestions to resolve this issue would be greatly appreciated.

Additional Context:

  • Using Spring Boot 3.0.9
  • oauth1-signer-java version 1.5.3
  • Java 17

Incorrect Parameter-Encoding in Authorization-String

Hi,

according to my understanding of the OAuth1.0a standard this implementation seems to have issues with the parameter encoding.

Let's have a look at [1] or even at [2] which is referenced in the JavaDoc of the implementation.
In [1] in 5.4.1 Authorization Header we find the following instruction:

Parameter names and values are encoded per Parameter Encoding.

Parameter Encoding is defined in 5.1 as:

All parameter names and values are escaped using the [RFC3986] percent-encoding (%xx) mechanism. Characters not in the unreserved character set ([RFC3986] section 2.3) MUST be encoded. Characters in the unreserved character set MUST NOT be encoded. Hexadecimal characters in encodings MUST be upper case. Text names and values MUST be encoded as UTF-8 octets before percent-encoding them per [RFC3629].
unreserved = ALPHA, DIGIT, '-', '.', '_', '~'

This is also confirmed by the referenced RFC [2] in 3.5.1 and 3.6.

Now, in the implementation, in method
public static String getAuthorizationHeader(URI uri, String method, String payload, Charset charset, String consumerKey, PrivateKey signingKey)
we can see that no parameter is percent-encoded except the created oauth_signature. For most of the parameters, this is not a problem as the unencoded form equals the encoded form. However, for base64-encoded parameters, e.g. the oauth_body_hash, this can be a problem in many cases since the characters "+","/" and "=" have to be percent-encoded. And this is not the case in this implementation.

What do you think? Am I right or did I miss some important point ?
Thanks a lot!

[1] https://oauth.net/core/1.0a/
[2] https://tools.ietf.org/html/rfc5849#section-3.5.1

The JAR throws "no such provider: SunJSSE" in IBM Websphere Runtime environment

This line of code in AuthenticationUtils results in a "provider not found" exception in Websphere.
KeyStore.getInstance("PKCS12", "SunJSSE");

In Websphere, the equivalent provider is com.ibm.jsse2.IBMJSSEProvider2

According to Oracle documentation -

General purpose applications SHOULD NOT request cryptographic services from specific providers
getInstance("...", "SunJCE"); // not recommended
// ... versus ...
getInstance("..."); // recommended

https://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html.

The code should say, KeyStore.getInstance("PKCS12") and allow the runtime to select security providers based on the environment.

[REQ] Feature Request Description

Is your feature request related to a problem? Please describe.

A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Describe the solution you'd like

A clear and concise description of what you want to happen.

Describe alternatives you've considered

A clear and concise description of any alternative solutions or features you've considered.

Additional context

Add any other context or screenshots about the feature request here.

Wrong base URI string is calculated when the provided URI contains url-encoded characters

According to https://tools.ietf.org/html/rfc5849#section-3.4.1.2:

The HTTP request:

 GET /r%20v/X?id=123 HTTP/1.1
 Host: EXAMPLE.COM:80

is represented by the base string URI: "http://example.com/r%20v/X" and not "http://example.com/r%2520v/X" as currently computed from the lib.

See also:

uri = URI.create("http://EXAMPLE.COM:80/r%20v/X?id=123");
baseUri = OAuth.getBaseUriString(uri);
// /!\ According to https://tools.ietf.org/html/rfc5849#section-3.4.1.2 it seems we should get "r%20v", not "r%2520v"
assertEquals("http://example.com/r%2520v/X", baseUri);


A failing example:

  1. Client want to make this request: https://example.com/api/foo@bar integrating the lib with an OpenAPI Generated API Client
  2. The call builder (in OpenAPI generated client) encode the URL, calling URLEncoder.encode(). Since '@' is a reserved character as per RFC 3986, the URL will be encoded to https://example.com/api/foo%40bar
  3. The OAuth1.0a lib normalize the URL as per https://tools.ietf.org/html/rfc5849#section-3.4.1.2

Expected behaviour:

4a. The normalized base URI string should be: https://example.com/api/foo%40bar

Actual behaviour:

4b. The normalized base URI string calculated in the below method is: https://example.com/api/foo%2540bar

static String getBaseUriString(URI uri) {


How some of the other libs are calculating the base URI string:

[REQ] Split OAuth.java internals to not depend on Java PrivateKey interface (or by default software keys)

Is your feature request related to a problem? Please describe.

Would love to sign requests with a hardware key while not using JCA keys. That is, I'd like to get access to the DTBS or hash of it separately from the all-in-one "sign and get header value" operation. It seems (see #18 (comment)) that providing access to exactly this kind of serialization is exactly why this library exists.

Describe the solution you'd like

Split OAuth.java to a) generate the "data to be signed" and/or hash of it b) provide a way to provide the signature value (binary) for generating the string representations and attaching to a request. (c) don't generate the nonce internally or provide a way to provide this nonce from caller, or from a specific random source, and keep it as byte[] internally)

Alternatively, a small functional "signature provider" (or maybe Function if 1.8 could be targeted, which I see is not the case from comments and code specifically existing for 1.6) interface might make sense, that would be less "demanding" than implementing JCA PrivateKey mockups for API based signing.

While the code is generously MIT and I just copied the OAuth java (also Util.java due to the "I don't want to research if the .replace() calls in URLEncodign are needed or not) it would be nice if the library could be re-used (developers like the interceptors and signers for various client libs) yet support non-JCA approaches to hardware based signing.

OpenFeignSigner NPE

When doing GET requests (with no payload) the OpenFeignSigner is throwing an NPE

A wrong signature base string is generated when the provided URI contains non-encoded characters

Working example (request is accepted)

GET /?param=token1%3Atoken2 HTTP/1.1
Host: example.com
  • URI: URI.create("https://example.com/?param=token1%3Atoken2")
  • Base string: GET&https%3A%2F%2Fexample.com&param%3Dtoken1%253Atoken2

Issue reproduction (request is rejected)

GET /?param=token1:token2 HTTP/1.1
Host: example.com
  • URI: URI.create("https://example.com/?param=token1:token2")
  • Actual base string: GET&https%3A%2F%2Fexample.com&param%3Dtoken1%253Atoken2
  • Expected base string: GET&https%3A%2F%2Fexample.com&param%3Dtoken1%3Atoken2

Update to Java7

Since the Mastercard API gateway requires TLS 1.2 as minimum which is not supported by Java6 out of the box, there is no point maintaining 1.6 compatibility.

[BUG] OAuth hash seems to be generated differently per OS

Bug Report Checklist

  • [Y] Have you provided a code sample to reproduce the issue?
  • [Y ] Have you tested with the latest release to confirm the issue still exists?
  • [Y] Have you searched for related issues/PRs?
  • [Y] What's the actual output vs expected output?

Description
I am unable to access Sandbox MDES environment. I tried debugging my issue with MC Support, Case# 04022094.
It simply confirmed that whatever configuration I have seems correct. Then I came to a conclusion that the issue must be system dependent. To prove it I have prepared a simple project, copying your code from:
com.mastercard.developer.oauth.OAuth.getBodyHash(String, Charset, String)
And replacing improper usage of proprietary sun classes with appropriate one (anyhow, same result is produced, at least for my case).

To Reproduce
Simply run: mvn clean install, on the attached module.
mdes-test.zip

Expected behavior
Test in that class should display string: 6ZfZQnr07GwaA/sSwqVKGSVyriLSiHoMK+zxL3ZtKYo=
While I am receiving an actual string: 89jfVv9wBxgaBZ0CV8b9XNd7yRtw+IOVub7OEedTVx0=

Screenshots
No screenshot is needed.
Sandbox API responds with error that value calculated by Mastercard (expected string above) differs from the one received (actual string above).

Additional context
openjdk version "1.8.0_345"
OpenJDK Runtime Environment (Temurin)(build 1.8.0_345-b01)
OpenJDK 64-Bit Server VM (Temurin)(build 25.345-b01, mixed mode)

Related issues/PRs
There aren't related issues.

Suggest a fix/enhancement
I believe that code in: com.mastercard.developer.oauth.OAuth.getBodyHash(String, Charset, String)
is giving different results per either OS or JDK version, surely there is a mismatch.

[BUG] Oauth Body Hash generated is not matching when the payload has a forward slash

Bug Report Checklist

  • Have you provided a code sample to reproduce the issue?
  • Have you tested with the latest release to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?

Description
If any attributes of the request payload has a forward slash ,the body hash generated is different and the actual call to master card consumer clarity API is failing with error incorrect body hash

To Reproduce
Use a payload with forward slash in any of request attributes , and verify getAuthorizationHeader method

Expected behavior
Oauth body hash should be correct

Screenshots
If applicable, add screenshots to help explain your problem.

Additional context
Add any other context about the problem here (OS, language version, etc..).

Related issues/PRs
Has a similar issue/PR been reported/opened before? No

Suggest a fix/enhancement
If you can't fix the bug yourself, perhaps you can point to what might be causing the problem (line of code or commit), or simply make a suggestion.

A "Body hash parameter missing" error can be raised for POST requests with empty body

https://developer.mastercard.com/page/rest-authentication-requirements explains:

oauth_body_hash (...) should only be present on POST/PUT requests that contain a body

But when consuming the Merchant Point of Interest service the following error is returned to the REST client:

{
     "Source": "OAuth.BodyHash",
     "ReasonCode": "INVALID_INPUT_FORMAT",
     "Description": "Body hash parameter missing. ERROR CODE 600"
}

https://tools.ietf.org/id/draft-eaton-oauth-bodyhash-00.html#anchor4 explains:

If the request does not have an entity body, the hash should be taken over the empty string.

Indeed, adding "oauth_body_hash=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=" to the OAuth string (SHA256 of an empty string) seems to solve the problem.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.