Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/workflows/sonarqube.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ on:
pull_request:
branches:
- '*'
push:
branches:
- master

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand Down
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ dependencies {
include project(':events')
include project(':events-domain')
include project(':api')
include project(':http-api')
include project(':http')
}

def javadocSourceProjects = providers.provider {
Expand Down
1 change: 1 addition & 0 deletions http-api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
100 changes: 100 additions & 0 deletions http-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# HTTP API module

Public contracts and configuration types for the HTTP client.
These types are exposed to SDK consumers through the `:main` module's `api` dependency.

## `HttpClientConfiguration`

Bundles all HTTP client settings into a single object:

```java
HttpClientConfiguration config = HttpClientConfiguration.builder()
.connectionTimeout(15_000)
.readTimeout(15_000)
.proxy(proxy)
.proxyAuthenticator(authenticator)
.certificatePinningConfiguration(pinConfig)
.developmentSslConfig(devSsl)
.build();
```

## Proxy configuration

### Basic auth

```java
HttpProxy proxy = HttpProxy.newBuilder("proxy.example.com", 8080)
.basicAuth("user", "pass")
.build();
```

### mTLS with custom CA

```java
HttpProxy proxy = HttpProxy.newBuilder("proxy.example.com", 8443)
.proxyCacert(caCertInputStream)
.mtls(clientCertInputStream, clientKeyInputStream)
.build();
```

### Custom credentials provider

```java
// Bearer token
HttpProxy proxy = HttpProxy.newBuilder("proxy.example.com", 8080)
.credentialsProvider(() -> fetchBearerToken())
.build();

// Basic credentials
HttpProxy proxy = HttpProxy.newBuilder("proxy.example.com", 8080)
.credentialsProvider(new BasicCredentialsProvider() {
public String getUsername() { return "user"; }
public String getPassword() { return "pass"; }
})
.build();
```

## Custom proxy authenticator

Implement `SplitAuthenticator` to handle proxy challenge/response flows:

```java
SplitAuthenticator authenticator = new SplitAuthenticator() {
@Override
public AuthenticatedRequest authenticate(@NonNull AuthenticatedRequest request) {
request.setHeader("Proxy-Authorization", "Bearer " + getToken());
return request;
}
};
```

The `AuthenticatedRequest` gives access to existing headers and the request URL, so the authenticator can make decisions based on context.

## Certificate pinning

```java
CertificatePinningConfiguration pinConfig = CertificatePinningConfiguration.builder()
// Pin by hash (sha256 or sha1)
.addPin("sdk.split.io", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
// Pin from a certificate file (derives hashes automatically)
.addPin("*.split.io", certInputStream)
// Optional: get notified on pin failures
.failureListener((host, certificateChain) -> {
Log.w("Split", "Pin failed for " + host
+ ", chain size: " + certificateChain.size());
})
.build();
```

Wildcard hosts are supported: `*.example.com` matches one subdomain, `**.example.com` matches any depth.

## Development SSL overrides

For test environments with self-signed certificates:

```java
DevelopmentSslConfig devSsl = new DevelopmentSslConfig(trustManager, hostnameVerifier);

// Or, if you already have an SSLSocketFactory:
DevelopmentSslConfig devSsl = new DevelopmentSslConfig(sslSocketFactory, trustManager, hostnameVerifier);
```
24 changes: 24 additions & 0 deletions http-api/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
plugins {
id 'com.android.library'
}

apply from: "$rootDir/gradle/common-android-library.gradle"

android {
namespace 'io.split.android.client.network.api'

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

dependencies {
implementation libs.annotation
implementation project(':logger')

testImplementation libs.junit4
testImplementation libs.mockitoCore
testImplementation libs.mockitoInline
testImplementation libs.okhttpTls
}
1 change: 1 addition & 0 deletions http-api/consumer-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

21 changes: 21 additions & 0 deletions http-api/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
4 changes: 4 additions & 0 deletions http-api/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.split.android.client.network;

class Algorithm {

public static final String SHA256 = "sha256";
public static final String SHA1 = "sha1";
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import java.util.Map;

interface AuthenticatedRequest<T> {
public interface AuthenticatedRequest {

void setHeader(@NonNull String name, @NonNull String value);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.split.android.client.network;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public interface Authenticator {

@Nullable AuthenticatedRequest authenticate(@NonNull AuthenticatedRequest request);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
class CertificateCheckerHelper {

@Nullable
static Set<CertificatePin> getPinsForHost(String pattern, Map<String, Set<CertificatePin>> configuredPins) {
public static Set<CertificatePin> getPinsForHost(String pattern, Map<String, Set<CertificatePin>> configuredPins) {
Set<CertificatePin> hostPins = configuredPins.get(pattern);
Set<CertificatePin> wildcardPins = new LinkedHashSet<>();

Expand Down Expand Up @@ -53,7 +53,7 @@ static Set<CertificatePin> getPinsForHost(String pattern, Map<String, Set<Certif
}

@NonNull
static Set<CertificatePin> getPinsFromInputStream(InputStream inputStream, PinEncoder pinEncoder) {
public static Set<CertificatePin> getPinsFromInputStream(InputStream inputStream, PinEncoder pinEncoder) {
try (InputStream stream = inputStream) {
CertificateFactory factory = CertificateFactory.getInstance("X.509");

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
package io.split.android.client.network;

import com.google.gson.annotations.SerializedName;

import java.util.Arrays;
import java.util.Objects;

public class CertificatePin {

@SerializedName("pin")
private final byte[] mPin;
@SerializedName("algo")
private final String mAlgorithm;

CertificatePin(byte[] pin, String algorithm) {
public CertificatePin(byte[] pin, String algorithm) {
mPin = pin;
mAlgorithm = algorithm;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import java.util.Map;
import java.util.Set;

import io.split.android.client.utils.Base64Util;
import io.split.android.client.utils.logger.Logger;

public class CertificatePinningConfiguration {
Expand Down Expand Up @@ -160,7 +159,7 @@ public Builder failureListener(@NonNull CertificatePinningFailureListener failur
}

// Meant to be used only when setting up bg sync jobs
void addPins(String host, Set<CertificatePin> pins) {
public void addPins(String host, Set<CertificatePin> pins) {
if (host == null || host.trim().isEmpty()) {
Logger.e("Host cannot be null or empty. Ignoring entry");
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.split.android.client.network;

import android.util.Base64;

import io.split.android.client.utils.logger.Logger;

class DefaultBase64Decoder implements Base64Decoder {

@Override
public byte[] decode(String base64) {
try {
return Base64.decode(base64, Base64.DEFAULT);
} catch (IllegalArgumentException e) {
Logger.e("Received bytes didn't correspond to a valid Base64 encoded string." + e.getLocalizedMessage());
} catch (Exception e) {
Logger.e("An unknown error has occurred " + e.getLocalizedMessage());
}
return null;
}
}
Loading
Loading