Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.
3 changes: 3 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@ android {

dependencies {
compile "com.facebook.react:react-native:+"
//todo as required minimal sdk version will be more then 23, upgrade this to latest version
//see https://github.com/auth0/java-jwt/issues/131
compile 'com.auth0:java-jwt:2.2.2'
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.support.annotation.NonNull;

import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactPackage;
Expand All @@ -15,6 +17,7 @@
import org.json.JSONObject;

import java.io.File;
import java.io.NotActiveException;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -36,18 +39,24 @@ public class CodePush implements ReactPackage {

// Config properties.
private String mDeploymentKey;
private String mServerUrl = "https://codepush.azurewebsites.net/";
private static String mServerUrl = "https://codepush.azurewebsites.net/";

private Context mContext;
private final boolean mIsDebugMode;

private static String mPublicKey;

private static ReactInstanceHolder mReactInstanceHolder;
private static CodePush mCurrentInstance;

public CodePush(String deploymentKey, Context context) {
this(deploymentKey, context, false);
}

public static String getServiceUrl() {
return mServerUrl;
}

public CodePush(String deploymentKey, Context context, boolean isDebugMode) {
mContext = context.getApplicationContext();

Expand All @@ -72,11 +81,45 @@ public CodePush(String deploymentKey, Context context, boolean isDebugMode) {
initializeUpdateAfterRestart();
}

public CodePush(String deploymentKey, Context context, boolean isDebugMode, String serverUrl) {
public CodePush(String deploymentKey, Context context, boolean isDebugMode, @NonNull String serverUrl) {
this(deploymentKey, context, isDebugMode);
mServerUrl = serverUrl;
}

public CodePush(String deploymentKey, Context context, boolean isDebugMode, int publicKeyResourceDescriptor) {
this(deploymentKey, context, isDebugMode);

mPublicKey = getPublicKeyByResourceDescriptor(publicKeyResourceDescriptor);
}

public CodePush(String deploymentKey, Context context, boolean isDebugMode, @NonNull String serverUrl, Integer publicKeyResourceDescriptor) {
this(deploymentKey, context, isDebugMode);

if (publicKeyResourceDescriptor != null) {
mPublicKey = getPublicKeyByResourceDescriptor(publicKeyResourceDescriptor);
}

mServerUrl = serverUrl;
}

private String getPublicKeyByResourceDescriptor(int publicKeyResourceDescriptor){
String publicKey;
try {
publicKey = mContext.getString(publicKeyResourceDescriptor);
} catch (Resources.NotFoundException e) {
throw new CodePushInvalidPublicKeyException(
"Unable to get public key, related resource descriptor " +
publicKeyResourceDescriptor +
" can not be found", e
);
}

if (publicKey.isEmpty()) {
throw new CodePushInvalidPublicKeyException("Specified public key is empty");
}
return publicKey;
}

public void clearDebugCacheIfNeeded() {
if (mIsDebugMode && mSettingsManager.isPendingUpdate(null)) {
// This needs to be kept in sync with https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java#L78
Expand All @@ -99,6 +142,10 @@ public String getAssetsBundleFileName() {
return mAssetsBundleFileName;
}

public String getPublicKey() {
return mPublicKey;
}

long getBinaryResourcesModifiedTime() {
try {
String packageName = this.mContext.getPackageName();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.microsoft.codepush.react;

import android.content.Context;

public class CodePushBuilder {
private String mDeploymentKey;
private Context mContext;

private boolean mIsDebugMode;
private String mServerUrl;
private Integer mPublicKeyResourceDescriptor;

public CodePushBuilder(String deploymentKey, Context context) {
this.mDeploymentKey = deploymentKey;
this.mContext = context;
this.mServerUrl = CodePush.getServiceUrl();
}

public CodePushBuilder setIsDebugMode(boolean isDebugMode) {
this.mIsDebugMode = isDebugMode;
return this;
}

public CodePushBuilder setServerUrl(String serverUrl) {
this.mServerUrl = serverUrl;
return this;
}

public CodePushBuilder setPublicKeyResourceDescriptor(int publicKeyResourceDescriptor) {
this.mPublicKeyResourceDescriptor = publicKeyResourceDescriptor;
return this;
}

public CodePush build() {
return new CodePush(this.mDeploymentKey, this.mContext, this.mIsDebugMode, this.mServerUrl, this.mPublicKeyResourceDescriptor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ public class CodePushConstants {
public static final String STATUS_FILE = "codepush.json";
public static final String UNZIPPED_FOLDER_NAME = "unzipped";
public static final String CODE_PUSH_APK_BUILD_TIME_KEY = "CODE_PUSH_APK_BUILD_TIME";
public static final String BUNDLE_JWT_FILE = ".codepushrelease";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.microsoft.codepush.react;

class CodePushInvalidPublicKeyException extends RuntimeException {

public CodePushInvalidPublicKeyException(String message, Throwable cause) {
super(message, cause);
}

public CodePushInvalidPublicKeyException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ public void dispatchDownloadProgressEvent() {
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(CodePushConstants.DOWNLOAD_PROGRESS_EVENT_NAME, latestDownloadProgress.createWritableMap());
}
});
}, mCodePush.getPublicKey());

JSONObject newPackage = mUpdateManager.getPackage(CodePushUtils.tryGetString(updatePackage, CodePushConstants.PACKAGE_HASH_KEY));
promise.resolve(CodePushUtils.convertJsonObjectToWritable(newPackage));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public JSONObject getCurrentPackageInfo() {
return CodePushUtils.getJsonObjectFromFile(statusFilePath);
} catch (IOException e) {
// Should not happen.
throw new CodePushUnknownException("Error getting current package info" , e);
throw new CodePushUnknownException("Error getting current package info", e);
}
}

Expand All @@ -64,7 +64,7 @@ public void updateCurrentPackageInfo(JSONObject packageInfo) {
CodePushUtils.writeJsonToFile(packageInfo, getStatusFilePath());
} catch (IOException e) {
// Should not happen.
throw new CodePushUnknownException("Error updating current package info" , e);
throw new CodePushUnknownException("Error updating current package info", e);
}
}

Expand Down Expand Up @@ -116,16 +116,16 @@ public JSONObject getCurrentPackage() {
if (packageHash == null) {
return null;
}

return getPackage(packageHash);
}

public JSONObject getPreviousPackage() {
String packageHash = getPreviousPackageHash();
if (packageHash == null) {
return null;
}

return getPackage(packageHash);
}

Expand All @@ -140,7 +140,8 @@ public JSONObject getPackage(String packageHash) {
}

public void downloadPackage(JSONObject updatePackage, String expectedBundleFileName,
DownloadProgressCallback progressCallback) throws IOException {
DownloadProgressCallback progressCallback,
String stringPublicKey) throws IOException {
String newUpdateHash = updatePackage.optString(CodePushConstants.PACKAGE_HASH_KEY, null);
String newUpdateFolderPath = getPackageFolderPath(newUpdateHash);
String newUpdateMetadataPath = CodePushUtils.appendPathComponent(newUpdateFolderPath, CodePushConstants.PACKAGE_FILE_NAME);
Expand Down Expand Up @@ -179,7 +180,7 @@ public void downloadPackage(JSONObject updatePackage, String expectedBundleFileN
while ((numBytesRead = bin.read(data, 0, CodePushConstants.DOWNLOAD_BUFFER_SIZE)) >= 0) {
if (receivedBytes < 4) {
for (int i = 0; i < numBytesRead; i++) {
int headerOffset = (int)(receivedBytes) + i;
int headerOffset = (int) (receivedBytes) + i;
if (headerOffset >= 4) {
break;
}
Expand Down Expand Up @@ -244,7 +245,39 @@ public void downloadPackage(JSONObject updatePackage, String expectedBundleFileN
}

if (isDiffUpdate) {
CodePushUpdateUtils.verifyHashForDiffUpdate(newUpdateFolderPath, newUpdateHash);
CodePushUtils.log("Applying diff update.");
Copy link
Contributor

@max-mironov max-mironov Aug 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think if we ever need this if block here due to we can move this log statement (and this is the only thing that this block is doing now) to Line 255 right after new if (isDiffUpdate) block begins.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@max-mironov no, we can't, because logging should be executed anyway regardless signature verification enabled or not.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha! May be we can also state what kind of update is applying currently with
isDiffUpdate ? CodePushUtils.log("Applying diff update.") : CodePushUtils.log("Applying full update.") but that is completely up to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@max-mironov, good proposition, i think i'll do it your way ☝️

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@max-mironov changes added, thx!

} else {
CodePushUtils.log("Applying full update.");
}

boolean isSignatureVerificationEnabled = (stringPublicKey != null);

String signaturePath = CodePushUpdateUtils.getSignatureFilePath(newUpdateFolderPath);
boolean isSignatureAppearedInBundle = FileUtils.fileAtPathExists(signaturePath);

if (isSignatureVerificationEnabled) {
if (isSignatureAppearedInBundle) {
CodePushUpdateUtils.verifySignature(newUpdateFolderPath, stringPublicKey);
} else {
throw new CodePushInvalidUpdateException(
"Error! Public key was provided but there is no JWT signature within app bundle to verify. " +
"Possible reasons, why that might happen: \n" +
"1. You've been released CodePush bundle update using version of CodePush CLI that is not support code signing.\n" +
"2. You've been released CodePush bundle update without providing --privateKeyPath option."
);
}
} else {
if (isSignatureAppearedInBundle) {
CodePushUtils.log(
"Warning! JWT signature exists in codepush update but code integrity check couldn't be performed because there is no public key configured. " +
"Please ensure that public key is properly configured within your application."
);
CodePushUpdateUtils.verifyFolderHash(newUpdateFolderPath, newUpdateHash);
} else {
if (isDiffUpdate) {
CodePushUpdateUtils.verifyFolderHash(newUpdateFolderPath, newUpdateHash);
}
}
}

CodePushUtils.setJSONValueForKey(updatePackage, CodePushConstants.RELATIVE_BUNDLE_PATH_KEY, relativeBundlePath);
Expand Down
Loading