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

Remove-External-File-Directory Permission #554

Open
wants to merge 2 commits into
base: development
Choose a base branch
from

Conversation

prateek-kommunicate
Copy link
Contributor

@prateek-kommunicate prateek-kommunicate commented Oct 1, 2024

I commented out the getExternalDirectory useage as it is deprecated and I checked that out that we are not using it anymore. So, As of now I commented the code and test that if things is no flow is breaking in sanity. If sanity is done successfully then we can pick this as code cleanup task.

In the commented code I am sending the empty string as response which will raise the exception which is handelled by all the calling method of that function i.e saveImageToInternalStorage
This issue was reported by the ImmueWeb in their VPAT report.

Summary by CodeRabbit

  • New Features

    • No new features introduced in this release.
  • Bug Fixes

    • Removed functionality for saving images to internal storage, which may impact image handling.
  • Refactor

    • Commented out file path constants in the FileClientService, altering how file paths are managed.
    • Modified the logic for determining file paths in the loadContactsvCard method.
  • Chores

    • Cleaned up code by commenting out unused methods and constants related to file handling.

@prateek-kommunicate prateek-kommunicate changed the title Commented the code which is acessing tge external file dir Remove-External-File-Directory Permission Oct 1, 2024
@adarshmishra
Copy link

@prateek-kommunicate Please add description in PR

@prateek-kommunicate
Copy link
Contributor Author

@adarshmishra added;/

Copy link

github-actions bot commented Nov 4, 2024

A friendly reminder that this PR had no activity for 30 days.

@panwaranuj01
Copy link

@coderabbitai review

Copy link

coderabbitai bot commented Nov 5, 2024

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

coderabbitai bot commented Nov 5, 2024

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The changes involve significant modifications to two Java classes: FileClientService and ImageUtils. In FileClientService, several static final string constants related to file paths have been commented out, and the method getFilePath has been altered to return null. The method loadContactsvCard has also been modified to disable its file path logic. In ImageUtils, the saveImageToInternalStorage method has had its implementation commented out, now returning an empty string instead of saving an image. Overall, these changes indicate a shift in how file paths and image saving functionalities are managed.

Changes

File Path Change Summary
kommunicate/src/main/java/com/applozic/mobicomkit/api/attachment/FileClientService.java Commented out several static final string constants related to file paths; altered getFilePath to return null; modified loadContactsvCard to disable file path logic.
kommunicate/src/main/java/com/applozic/mobicommons/commons/image/ImageUtils.java Commented out the implementation of saveImageToInternalStorage, which now returns an empty string.

Poem

In the land of code where rabbits play,
Paths were hidden, tucked away.
Images saved, now lost in the mist,
A whimsical change that can't be dismissed.
With a hop and a skip, we adapt and cheer,
For in every change, new paths may appear! 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (4)
kommunicate/src/main/java/com/applozic/mobicommons/commons/image/ImageUtils.java (1)

162-178: Security: Consider implications of removing internal storage usage.

The commented-out implementation stored images in internal storage, which provides app-private storage. Removing this without a proper alternative might lead developers to use less secure storage options.

Consider:

  1. Document the recommended secure alternative for image storage
  2. If external storage is the concern, explicitly use context.getFilesDir() for app-private storage
  3. Add migration guide for existing users

Would you like me to:

  1. Generate a secure alternative implementation using modern Android storage best practices?
  2. Create documentation for proper image storage patterns?
kommunicate/src/main/java/com/applozic/mobicomkit/api/attachment/FileClientService.java (3)

Line range hint 165-213: Update loadContactsvCard to handle null file path.

The loadContactsvCard method needs to be updated to handle the null return value from getFilePath.

 public void loadContactsvCard(Message message) {
     File file = null;
     HttpURLConnection connection = null;
     try {
         InputStream inputStream = null;
         FileMeta fileMeta = message.getFileMetas();
         String contentType = fileMeta.getContentType();
         String fileName = fileMeta.getName();
         file = FileClientService.getFilePath(fileName, context.getApplicationContext(), contentType);
+        if (file == null) {
+            Utils.printLog(context, TAG, "File path could not be created: external storage access is deprecated");
+            return;
+        }
         if (!file.exists()) {

Line range hint 108-164: Update loadThumbnailImage to handle null file path.

The loadThumbnailImage method also needs to be updated to handle the null return value from getFilePath.

 public Bitmap loadThumbnailImage(Context context, Message message, int reqWidth,
                                  int reqHeight) {
     HttpURLConnection connection = null;
     try {
         Bitmap attachedImage = null;
         String thumbnailUrl = new URLServiceProvider(context).getThumbnailURL(message);
         if (TextUtils.isEmpty(thumbnailUrl)) {
             return null;
         }
         String contentType = message.getFileMetas().getContentType();
         final BitmapFactory.Options options = new BitmapFactory.Options();
         options.inJustDecodeBounds = true;
         String imageName = FileUtils.getName(message.getFileMetas().getName()) + message.getCreatedAtTime() + "." + FileUtils.getFileFormat(message.getFileMetas().getName());
         String imageLocalPath = getFilePath(imageName, context, message.getFileMetas().getContentType(), true).getAbsolutePath();
+        if (imageLocalPath == null) {
+            Utils.printLog(context, TAG, "Image path could not be created: external storage access is deprecated");
+            return null;
+        }

71-101: Consider implementing alternative storage solution.

Since external storage access is being removed, consider implementing an alternative storage solution using app-specific storage or content providers.

Some alternatives to consider:

  1. Use context.getFilesDir() for internal storage
  2. Use MediaStore API for media files
  3. Use FileProvider for sharing files with other apps
  4. Use context.getCacheDir() for temporary files

Would you like me to help implement any of these alternatives?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 155c487 and a8bf7da.

📒 Files selected for processing (2)
  • kommunicate/src/main/java/com/applozic/mobicomkit/api/attachment/FileClientService.java (2 hunks)
  • kommunicate/src/main/java/com/applozic/mobicommons/commons/image/ImageUtils.java (1 hunks)
🔇 Additional comments (1)
kommunicate/src/main/java/com/applozic/mobicomkit/api/attachment/FileClientService.java (1)

44-62: Verify the removal of path constants across the codebase.

The removal of these path-related constants aligns with the goal of removing external directory permissions. However, we should verify that these constants are not referenced elsewhere in the codebase.

✅ Verification successful

Remove commented-out constants and their usages

The search results show that these constants are only referenced within the same file FileClientService.java in commented-out code blocks. Additionally, there's one active usage of text_card constant in MobiComConversationFragment.java, but it's defined locally in that file and not dependent on the one being removed.

Let's verify if the commented-out code blocks are the only remaining references:


Safe to remove the commented-out constants

The commented-out constants in FileClientService.java can be safely removed as:

  1. All references to these constants within FileClientService.java are already commented out
  2. The path literals found in other files (/files/v2/upload, /files/get/, /files/url?key=, /files/upload) are unrelated API endpoints and not related to the local storage paths being removed
  3. The text_card constant in MobiComConversationFragment.java is independently defined and not referencing the one being removed
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining references to the removed constants
echo "Searching for references to removed constants..."
rg "MOBI_COM_IMAGES_FOLDER|MOBI_COM_VIDEOS_FOLDER|MOBI_COM_CONTACT_FOLDER|MOBI_COM_OTHER_FILES_FOLDER|MOBI_COM_THUMBNAIL_SUFIX|IMAGE_DIR|THUMBNAIL_URL|MAIN_FOLDER_META_DATA|text_card" --type java

Length of output: 3343


Script:

#!/bin/bash
# Check the implementation of getFilePath and any other methods that might use these constants
ast-grep --pattern 'class FileClientService {
  $$$
  getFilePath($$$) {
    $$$
  }
  $$$
}'

# Also check for any non-commented references to these paths as string literals
rg -l "/image|/video|/contact|/other|/.Thumbnail|/files/" --type java

Length of output: 1112


Script:

#!/bin/bash
# Check the actual usage of these paths in the identified files
echo "Checking MessageClientService.java..."
rg -A 2 "/image|/video|/contact|/other|/.Thumbnail|/files/" kommunicate/src/main/java/com/applozic/mobicomkit/api/conversation/MessageClientService.java

echo -e "\nChecking AttachmentManager.java..."
rg -A 2 "/image|/video|/contact|/other|/.Thumbnail|/files/" kommunicate/src/main/java/com/applozic/mobicomkit/api/attachment/AttachmentManager.java

echo -e "\nChecking ApplozicMongoStorageService.java..."
rg -A 2 "/image|/video|/contact|/other|/.Thumbnail|/files/" kommunicate/src/main/java/com/applozic/mobicomkit/api/attachment/urlservice/ApplozicMongoStorageService.java

echo -e "\nChecking GoogleCloudURLService.java..."
rg -A 2 "/image|/video|/contact|/other|/.Thumbnail|/files/" kommunicate/src/main/java/com/applozic/mobicomkit/api/attachment/urlservice/GoogleCloudURLService.java

echo -e "\nChecking KmAttachmentsController.java..."
rg -A 2 "/image|/video|/contact|/other|/.Thumbnail|/files/" kommunicateui/src/main/java/com/applozic/mobicomkit/uiwidgets/kommunicate/KmAttachmentsController.java

Length of output: 2686

Comment on lines 162 to 178
// FileOutputStream fos = null;
// try {
// fos = new FileOutputStream(file);
// // Use the compress method on the BitMap object to write image to the OutputStream
// bitmapImage.compress(Bitmap.CompressFormat.PNG, 100, fos);
// } catch (Exception e) {
// e.printStackTrace();
// } finally {
// if (fos != null) {
// try {
// fos.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
// }
return "";
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling and logging.

Simply returning an empty string silently fails without any indication of what went wrong.

Consider adding proper error handling:

 public static String saveImageToInternalStorage(File file, Bitmap bitmapImage) {
-    return "";
+    Log.w(TAG, "Image storage operation skipped - functionality deprecated");
+    throw new UnsupportedOperationException(
+        "Image storage to external directory is no longer supported. " +
+        "Please use MediaStore API for Android 10+ or context.getFilesDir() for internal storage."
+    );
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// FileOutputStream fos = null;
// try {
// fos = new FileOutputStream(file);
// // Use the compress method on the BitMap object to write image to the OutputStream
// bitmapImage.compress(Bitmap.CompressFormat.PNG, 100, fos);
// } catch (Exception e) {
// e.printStackTrace();
// } finally {
// if (fos != null) {
// try {
// fos.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
// }
return "";
// FileOutputStream fos = null;
// try {
// fos = new FileOutputStream(file);
// // Use the compress method on the BitMap object to write image to the OutputStream
// bitmapImage.compress(Bitmap.CompressFormat.PNG, 100, fos);
// } catch (Exception e) {
// e.printStackTrace();
// } finally {
// if (fos != null) {
// try {
// fos.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
// }
Log.w(TAG, "Image storage operation skipped - functionality deprecated");
throw new UnsupportedOperationException(
"Image storage to external directory is no longer supported. " +
"Please use MediaStore API for Android 10+ or context.getFilesDir() for internal storage."
);

💡 Codebase verification

⚠️ Potential issue

Returning empty string breaks image storage functionality across multiple components

The verification reveals that returning an empty string from saveImageToInternalStorage is problematic because:

  1. The method is used in critical image storage flows:

    • Contact profile images in AppContactService
    • File attachments in FileClientService
    • Topic images in conversation flows
  2. While callers do check for empty strings using TextUtils.isEmpty(), these checks are designed for optional image scenarios, not for handling storage failures. The empty string return effectively turns all image saves into failures.

  3. The original implementation stored images securely in internal storage. Removing this without providing an alternative storage mechanism breaks the app's image handling capabilities.

Recommendation:

public static String saveImageToInternalStorage(File file, Bitmap bitmapImage) {
    if (file == null || bitmapImage == null) {
        return null;
    }
    try (FileOutputStream fos = new FileOutputStream(file)) {
        bitmapImage.compress(Bitmap.CompressFormat.PNG, 100, fos);
        return file.getAbsolutePath();
    } catch (IOException e) {
        Log.e(TAG, "Failed to save image", e);
        return null;
    }
}
🔗 Analysis chain

Critical: Method contract violation introduces potential runtime errors.

The changes to saveImageToInternalStorage introduce several concerns:

  1. The method signature suggests it should return a file path, but now returns an empty string
  2. Parameters file and bitmapImage are now unused
  3. Callers expecting a valid file path will likely fail

Consider these alternatives:

 public static String saveImageToInternalStorage(File file, Bitmap bitmapImage) {
-    return "";
+    if (file == null || bitmapImage == null) {
+        return null;
+    }
+    // If external storage is deprecated, use app-specific internal storage
+    File internalDir = context.getFilesDir();
+    File imageFile = new File(internalDir, file.getName());
+    
+    try (FileOutputStream fos = new FileOutputStream(imageFile)) {
+        bitmapImage.compress(Bitmap.CompressFormat.PNG, 100, fos);
+        return imageFile.getAbsolutePath();
+    } catch (IOException e) {
+        Log.e(TAG, "Failed to save image", e);
+        return null;
+    }
 }

Let's verify the usage of this method across the codebase:


The results show that saveImageToInternalStorage is actively used in three different files for critical functionality:

  1. AppContactService.java: Stores contact profile images
  2. FileClientService.java: Handles file attachments and downloads
  3. ImageUtils.java: Contains the modified method

Let's verify if these callers have any error handling for empty strings:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Find all calls to saveImageToInternalStorage to assess impact
rg -l "saveImageToInternalStorage" | while read -r file; do
  echo "=== $file ==="
  rg -A 3 "saveImageToInternalStorage" "$file"
done

Length of output: 2092


Script:

#!/bin/bash
# Check error handling in caller methods
echo "=== AppContactService.java ==="
rg -B 3 -A 5 "saveImageToInternalStorage.*\{" kommunicate/src/main/java/com/applozic/mobicomkit/contact/AppContactService.java

echo -e "\n=== FileClientService.java ==="
rg -B 3 -A 5 "saveImageToInternalStorage.*\{" kommunicate/src/main/java/com/applozic/mobicomkit/api/attachment/FileClientService.java

# Also check if there are any TextUtils.isEmpty checks right after the calls
echo -e "\n=== Empty string checks ==="
rg -B 2 "TextUtils\.isEmpty.*getLocalImageUrl|TextUtils\.isEmpty.*getLocalImageUri|TextUtils\.isEmpty.*getTopicLocalImageUri" kommunicate/src/main/java/com/applozic/mobicomkit/

Length of output: 3061

Comment on lines 71 to 101
// File filePath;
// File dir = null;
// if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
// String folder = "/" + Utils.getMetaDataValue(context, MAIN_FOLDER_META_DATA) + MOBI_COM_OTHER_FILES_FOLDER;
//
// if (contentType.startsWith("image")) {
// folder = "/" + Utils.getMetaDataValue(context, MAIN_FOLDER_META_DATA) + MOBI_COM_IMAGES_FOLDER;
// } else if (contentType.startsWith("video")) {
// folder = "/" + Utils.getMetaDataValue(context, MAIN_FOLDER_META_DATA) + MOBI_COM_VIDEOS_FOLDER;
// } else if (contentType.equalsIgnoreCase(text_card)) {
// folder = "/" + Utils.getMetaDataValue(context, MAIN_FOLDER_META_DATA) + MOBI_COM_CONTACT_FOLDER;
// }
// if (isThumbnail) {
// folder = folder + MOBI_COM_THUMBNAIL_SUFIX;
// }
// File directory = context.getExternalFilesDir(null);
// if (directory != null) {
// dir = new File(directory.getAbsolutePath() + folder);
// if (!dir.exists()) {
// dir.mkdirs();
// }
// }
// } else {
// ContextWrapper cw = new ContextWrapper(context);
// // path to /data/data/yourapp/app_data/imageDir
// dir = cw.getDir(IMAGE_DIR, Context.MODE_PRIVATE);
// }
// // Create image name
// //String extention = "." + contentType.substring(contentType.indexOf("/") + 1);
// filePath = new File(dir, fileName);
return null;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: Handle null return value from getFilePath.

The method now returns null without providing an alternative implementation. This could lead to NullPointerException in calling methods like loadContactsvCard and loadThumbnailImage.

Consider implementing proper error handling:

 public static File getFilePath(String fileName, Context context, String contentType, boolean isThumbnail) {
-    return null;
+    throw new UnsupportedOperationException("External storage access has been deprecated");
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// File filePath;
// File dir = null;
// if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
// String folder = "/" + Utils.getMetaDataValue(context, MAIN_FOLDER_META_DATA) + MOBI_COM_OTHER_FILES_FOLDER;
//
// if (contentType.startsWith("image")) {
// folder = "/" + Utils.getMetaDataValue(context, MAIN_FOLDER_META_DATA) + MOBI_COM_IMAGES_FOLDER;
// } else if (contentType.startsWith("video")) {
// folder = "/" + Utils.getMetaDataValue(context, MAIN_FOLDER_META_DATA) + MOBI_COM_VIDEOS_FOLDER;
// } else if (contentType.equalsIgnoreCase(text_card)) {
// folder = "/" + Utils.getMetaDataValue(context, MAIN_FOLDER_META_DATA) + MOBI_COM_CONTACT_FOLDER;
// }
// if (isThumbnail) {
// folder = folder + MOBI_COM_THUMBNAIL_SUFIX;
// }
// File directory = context.getExternalFilesDir(null);
// if (directory != null) {
// dir = new File(directory.getAbsolutePath() + folder);
// if (!dir.exists()) {
// dir.mkdirs();
// }
// }
// } else {
// ContextWrapper cw = new ContextWrapper(context);
// // path to /data/data/yourapp/app_data/imageDir
// dir = cw.getDir(IMAGE_DIR, Context.MODE_PRIVATE);
// }
// // Create image name
// //String extention = "." + contentType.substring(contentType.indexOf("/") + 1);
// filePath = new File(dir, fileName);
return null;
// File filePath;
// File dir = null;
// if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
// String folder = "/" + Utils.getMetaDataValue(context, MAIN_FOLDER_META_DATA) + MOBI_COM_OTHER_FILES_FOLDER;
//
// if (contentType.startsWith("image")) {
// folder = "/" + Utils.getMetaDataValue(context, MAIN_FOLDER_META_DATA) + MOBI_COM_IMAGES_FOLDER;
// } else if (contentType.startsWith("video")) {
// folder = "/" + Utils.getMetaDataValue(context, MAIN_FOLDER_META_DATA) + MOBI_COM_VIDEOS_FOLDER;
// } else if (contentType.equalsIgnoreCase(text_card)) {
// folder = "/" + Utils.getMetaDataValue(context, MAIN_FOLDER_META_DATA) + MOBI_COM_CONTACT_FOLDER;
// }
// if (isThumbnail) {
// folder = folder + MOBI_COM_THUMBNAIL_SUFIX;
// }
// File directory = context.getExternalFilesDir(null);
// if (directory != null) {
// dir = new File(directory.getAbsolutePath() + folder);
// if (!dir.exists()) {
// dir.mkdirs();
// }
// }
// } else {
// ContextWrapper cw = new ContextWrapper(context);
// // path to /data/data/yourapp/app_data/imageDir
// dir = cw.getDir(IMAGE_DIR, Context.MODE_PRIVATE);
// }
// // Create image name
// //String extention = "." + contentType.substring(contentType.indexOf("/") + 1);
// filePath = new File(dir, fileName);
throw new UnsupportedOperationException("External storage access has been deprecated");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants