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

feat: Authenticate private resource requests #21331

Open
wants to merge 17 commits into
base: trunk
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -2444,6 +2444,7 @@ class EditPostActivity : LocaleAwareActivity(), EditorFragmentActivity, EditorIm
"postType" to postType,
"postTitle" to editPostRepository.getPost()?.title,
"postContent" to editPostRepository.getPost()?.content,
"siteURL" to site.url,
"siteApiRoot" to siteApiRoot,
"authHeader" to authHeader,
"siteApiNamespace" to siteApiNamespace,
Expand All @@ -2457,7 +2458,9 @@ class EditPostActivity : LocaleAwareActivity(), EditorFragmentActivity, EditorIm
gutenbergPropsBuilder,
jetpackFeatureRemovalPhaseHelper.shouldShowJetpackPoweredEditorFeatures(),
isNewGutenbergEditor,
settings
settings,
site.isPrivate || site.isComingSoon,
site.isPrivateWPComAtomic
)
}

Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ google-play-services-auth = '20.4.1'
google-services = '4.4.2'
gravatar = '1.0.0'
greenrobot-eventbus = '3.3.1'
gutenberg-kit = 'trunk-a58a46f3fbb892f311b562e3c122d7ef4ebbfe33'
gutenberg-kit = 'trunk-d6fbfc7bc28ae6db2cce09950f24bc3080374596'
gutenberg-mobile = 'v1.121.0'
indexos-media-for-mobile = '43a9026f0973a2f0a74fa813132f6a16f7499c3a'
jackson-databind = '2.12.7.1'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import android.view.inputmethod.InputMethodManager;
import android.webkit.URLUtil;
import android.webkit.ValueCallback;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
Expand Down Expand Up @@ -68,6 +70,7 @@
import org.wordpress.android.util.helpers.MediaFile;
import org.wordpress.android.util.helpers.MediaGallery;
import org.wordpress.aztec.IHistoryListener;
import org.wordpress.gutenberg.GutenbergRequestInterceptor;
import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.LogExceptionCallback;
import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergEmbedWebViewActivity;
import org.wordpress.mobile.WPAndroidGlue.GutenbergJsException;
Expand Down Expand Up @@ -97,18 +100,29 @@
import org.wordpress.gutenberg.GutenbergView.ContentChangeListener;
import org.wordpress.gutenberg.GutenbergWebViewPool;

import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;

import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;

import static org.wordpress.mobile.WPAndroidGlue.Media.createRNMediaUsingMimeType;

public class GutenbergEditorFragment extends EditorFragmentAbstract implements
Expand All @@ -117,7 +131,8 @@ public class GutenbergEditorFragment extends EditorFragmentAbstract implements
EditorThemeUpdateListener,
GutenbergDialogPositiveClickInterface,
GutenbergDialogNegativeClickInterface,
GutenbergNetworkConnectionListener {
GutenbergNetworkConnectionListener,
GutenbergRequestInterceptor {
@Nullable private GutenbergView mGutenbergView;
private static final String GUTENBERG_EDITOR_NAME = "gutenberg";
private static final String KEY_HTML_MODE_ENABLED = "KEY_HTML_MODE_ENABLED";
Expand Down Expand Up @@ -181,14 +196,19 @@ public class GutenbergEditorFragment extends EditorFragmentAbstract implements

private ProgressDialog mSavingContentProgressDialog;
@Nullable private static Map<String, Object> mSettings;
private static boolean mIsPrivate = false;
private static boolean mIsPrivateAtomic = false;
@NonNull OkHttpClient mHttpClient = new OkHttpClient();

public static GutenbergEditorFragment newInstance(Context context,
boolean isNewPost,
GutenbergWebViewAuthorizationData webViewAuthorizationData,
GutenbergPropsBuilder gutenbergPropsBuilder,
boolean jetpackFeaturesEnabled,
boolean newGutenbergEnabled,
@Nullable Map<String, Object> settings) {
@Nullable Map<String, Object> settings,
boolean isPrivate,
boolean isPrivateAtomic) {
GutenbergEditorFragment fragment = new GutenbergEditorFragment();
Bundle args = new Bundle();
args.putBoolean(ARG_IS_NEW_POST, isNewPost);
Expand All @@ -199,6 +219,8 @@ public static GutenbergEditorFragment newInstance(Context context,
SavedInstanceDatabase db = SavedInstanceDatabase.Companion.getDatabase(context);
mIsNewGutenbergEnabled = newGutenbergEnabled;
mSettings = settings;
mIsPrivate = isPrivate;
mIsPrivateAtomic = isPrivateAtomic;
if (db != null) {
db.addParcel(ARG_GUTENBERG_WEB_VIEW_AUTH_DATA, webViewAuthorizationData);
db.addParcel(ARG_GUTENBERG_PROPS_BUILDER, gutenbergPropsBuilder);
Expand Down Expand Up @@ -291,6 +313,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa
mGutenbergView.setEditorDidBecomeAvailable(view -> {
mEditorFragmentListener.onEditorFragmentContentReady(new ArrayList<Object>(), false);
});
mGutenbergView.setRequestInterceptor(this);

Integer postId = (Integer) mSettings.get("postId");
if (postId != null && postId == 0) {
Expand Down Expand Up @@ -1635,4 +1658,101 @@ public void onConnectionStatusChange(boolean isConnected) {
mEditorFragmentListener.onMediaRetryAll(mFailedMediaIds);
}
}

@Override
public boolean canIntercept(@NonNull WebResourceRequest request) {
Uri url = request.getUrl();

return mIsPrivate && isSiteHostedMediaFile(url.toString());
}

boolean isSiteHostedMediaFile(@NonNull String urlString) {
String siteURL = (String) (mSettings != null ? mSettings.get("siteURL") : "");
Set<String> mediaExtensions = new HashSet<>(Arrays.asList(
"jpg", "jpeg", "png", "gif", "bmp", "webp",
"mp4", "mov", "avi", "mkv",
"mp3", "wav", "flac"
));

try {
URL url = new URL(urlString);
URL siteUrlObj = new URL(siteURL);

// Check if the URL is from the same host as the site URL
if (!url.getHost().equalsIgnoreCase(siteUrlObj.getHost())) {
return false;
}

// Extract the file name and extension
String path = url.getPath();
int lastDotIndex = path.lastIndexOf('.');
if (lastDotIndex == -1) {
return false;
}

String extension = path.substring(lastDotIndex + 1).toLowerCase(Locale.ROOT);

// Check if the extension is in the list of media extensions
return mediaExtensions.contains(extension);
} catch (MalformedURLException e) {
// Handle invalid URLs
return false;
}
}

@Nullable @Override
public WebResourceResponse handleRequest(@NonNull WebResourceRequest request) {
Uri url = request.getUrl();

String proxyUrl = url.toString();
if (mIsPrivateAtomic) {
proxyUrl = getPrivateResourceProxyUrl(url);
Copy link
Member Author

Choose a reason for hiding this comment

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

Private Atomic sites require a proxy for request authentication (D38925-code).

}

try {
Request okHttpRequest = new Request.Builder()
.url(proxyUrl)
.headers(Headers.of(request.getRequestHeaders()))
.addHeader("Authorization", mSettings.get("authHeader").toString())
.build();

Response response = mHttpClient.newCall(okHttpRequest).execute();

ResponseBody body = response.body();
if (body == null) {
return null;
}

okhttp3.MediaType contentType = body.contentType();
if (contentType == null) {
return null;
}

return new WebResourceResponse(
contentType.toString(),
response.header("content-encoding"),
body.byteStream()
);
} catch (IOException e) {
// We don't need to handle this ourselves, just tell the WebView that
// we weren't able to fetch the resource
return null;
}
}

private static @NonNull String getPrivateResourceProxyUrl(@NonNull Uri url) {
Uri newUri = new Uri.Builder()
.scheme("https")
.authority("public-api.wordpress.com")
.appendPath("wpcom")
.appendPath("v2")
.appendPath("sites")
.appendPath(url.getAuthority())
.appendPath("atomic-auth-proxy")
.appendPath("file")
.appendEncodedPath(url.getPath().substring(1)) // Remove leading '/'
.build();

return newUri.toString();
}
}
Loading