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(android)(9_3_X): add "tapjacking" prevention features #11962

Merged
merged 4 commits into from
Sep 28, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.MotionEvent;
import androidx.appcompat.widget.AppCompatButton;

public class TiUIButton extends TiUIView
Expand All @@ -45,6 +46,16 @@ public TiUIButton(final TiViewProxy proxy)
super(proxy);
Log.d(TAG, "Creating a button", Log.DEBUG_MODE);
AppCompatButton btn = new AppCompatButton(proxy.getActivity()) {
@Override
public boolean onFilterTouchEventForSecurity(MotionEvent event)
{
boolean isTouchAllowed = super.onFilterTouchEventForSecurity(event);
if (!isTouchAllowed) {
fireEvent(TiC.EVENT_TOUCH_FILTERED, dictFromEvent(event));
}
return isTouchAllowed;
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
Expand Down
15 changes: 15 additions & 0 deletions android/titanium/src/java/org/appcelerator/titanium/TiC.java
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,11 @@ public class TiC
*/
public static final String EVENT_PROPERTY_MESSAGE = "message";

/**
* @module.api
*/
public static final String EVENT_PROPERTY_OBSCURED = "obscured";

/**
* @module.api
*/
Expand Down Expand Up @@ -684,6 +689,11 @@ public class TiC
*/
public static final String EVENT_TOUCH_END = "touchend";

/**
* @module.api
*/
public static final String EVENT_TOUCH_FILTERED = "touchfiltered";

/**
* @module.api
*/
Expand Down Expand Up @@ -1687,6 +1697,11 @@ public class TiC
*/
public static final String PROPERTY_FILTER_CASE_INSENSITIVE = "filterCaseInsensitive";

/**
* @module.api
*/
public static final String PROPERTY_FILTER_TOUCHES_WHEN_OBSCURED = "filterTouchesWhenObscured";

/**
* @module.api
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
// others
TiC.PROPERTY_FOCUSABLE,
TiC.PROPERTY_TOUCH_ENABLED,
TiC.PROPERTY_FILTER_TOUCHES_WHEN_OBSCURED,
TiC.PROPERTY_VISIBLE,
TiC.PROPERTY_ENABLED,
TiC.PROPERTY_OPACITY,
Expand Down Expand Up @@ -126,6 +127,7 @@ public TiViewProxy()
pendingAnimationLock = new Object();

defaultValues.put(TiC.PROPERTY_TOUCH_ENABLED, true);
defaultValues.put(TiC.PROPERTY_FILTER_TOUCHES_WHEN_OBSCURED, false);
defaultValues.put(TiC.PROPERTY_SOUND_EFFECTS_ENABLED, true);
defaultValues.put(TiC.PROPERTY_BACKGROUND_REPEAT, false);
defaultValues.put(TiC.PROPERTY_VISIBLE, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,8 @@ public void propertyChanged(String key, Object oldValue, Object newValue, KrollP
} else if (key.equals(TiC.PROPERTY_TOUCH_ENABLED)) {
nativeView.setEnabled(TiConvert.toBoolean(newValue));
doSetClickable(TiConvert.toBoolean(newValue));
} else if (key.equals(TiC.PROPERTY_FILTER_TOUCHES_WHEN_OBSCURED)) {
setFilterTouchesWhenObscured(TiConvert.toBoolean(newValue, false));
} else if (key.equals(TiC.PROPERTY_VISIBLE)) {
newValue = (newValue == null) ? false : newValue;
this.setVisibility(TiConvert.toBoolean(newValue) ? View.VISIBLE : View.INVISIBLE);
Expand Down Expand Up @@ -1022,6 +1024,11 @@ public void processProperties(KrollDict d)
applyTouchFeedback((colorString != null) ? TiConvert.toColor(colorString) : null);
}

if (d.containsKey(TiC.PROPERTY_FILTER_TOUCHES_WHEN_OBSCURED) && !nativeViewNull) {
setFilterTouchesWhenObscured(
TiConvert.toBoolean(d.get(TiC.PROPERTY_FILTER_TOUCHES_WHEN_OBSCURED), false));
}

if (d.containsKey(TiC.PROPERTY_HIDDEN_BEHAVIOR) && !nativeViewNull) {
Object hidden = d.get(TiC.PROPERTY_HIDDEN_BEHAVIOR);
if (hidden != null) {
Expand Down Expand Up @@ -1551,6 +1558,43 @@ private void handleBorderProperty(String property, Object value)
motionEvents.put(MotionEvent.ACTION_CANCEL, TiC.EVENT_TOUCH_CANCEL);
}

private void setFilterTouchesWhenObscured(boolean isEnabled)
{
// Validate.
if (this.nativeView == null) {
return;
}

// Enable/disable tapjacking filter.
this.nativeView.setFilterTouchesWhenObscured(isEnabled);

// Android 4.4.2 and older has a bug where the above method sets it to the opposite.
// Google fixed it in Android 4.4.3, but we can't detect that patch version via API Level.
if ((Build.VERSION.SDK_INT < 21) && (isEnabled != this.nativeView.getFilterTouchesWhenObscured())) {
this.nativeView.setFilterTouchesWhenObscured(!isEnabled);
}
}

/**
* Determines if touch event was obscurred by an overlapping translucent window belonging to another app.
* This is used for security purposes to detect "tapjacking".
* @param event The touch event to be analyzed. Can be null.
* @return Returns true if touch event was obscurred. Returns false if not or if given a null argument.
*/
private boolean wasObscured(MotionEvent event)
{
if (event != null) {
int flags = event.getFlags();
if ((flags & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
return true;
}
if ((Build.VERSION.SDK_INT >= 29) && ((flags & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED) != 0)) {
return true;
}
}
return false;
}

protected KrollDict dictFromEvent(MotionEvent e)
{
TiDimension xDimension = new TiDimension((double) e.getX(), TiDimension.TYPE_LEFT);
Expand All @@ -1560,6 +1604,7 @@ protected KrollDict dictFromEvent(MotionEvent e)
data.put(TiC.EVENT_PROPERTY_X, xDimension.getAsDefault(this.nativeView));
data.put(TiC.EVENT_PROPERTY_Y, yDimension.getAsDefault(this.nativeView));
data.put(TiC.EVENT_PROPERTY_FORCE, (double) e.getPressure());
data.put(TiC.EVENT_PROPERTY_OBSCURED, wasObscured(e));
data.put(TiC.EVENT_PROPERTY_SIZE, (double) e.getSize());
data.put(TiC.EVENT_PROPERTY_SOURCE, proxy);
return data;
Expand All @@ -1583,6 +1628,11 @@ protected KrollDict dictFromEvent(KrollDict dictToCopy)
} else {
data.put(TiC.EVENT_PROPERTY_FORCE, (double) 0);
}
if (dictToCopy.containsKey(TiC.EVENT_PROPERTY_OBSCURED)) {
data.put(TiC.EVENT_PROPERTY_OBSCURED, dictToCopy.get(TiC.EVENT_PROPERTY_OBSCURED));
} else {
data.put(TiC.EVENT_PROPERTY_OBSCURED, false);
}
if (dictToCopy.containsKey(TiC.EVENT_PROPERTY_SIZE)) {
data.put(TiC.EVENT_PROPERTY_SIZE, dictToCopy.get(TiC.EVENT_PROPERTY_SIZE));
} else {
Expand Down Expand Up @@ -1743,6 +1793,7 @@ public boolean onTouch(View view, MotionEvent event)
TiDimension yDimension = new TiDimension((double) event.getY(), TiDimension.TYPE_TOP);
lastUpEvent.put(TiC.EVENT_PROPERTY_X, xDimension.getAsDefault(view));
lastUpEvent.put(TiC.EVENT_PROPERTY_Y, yDimension.getAsDefault(view));
lastUpEvent.put(TiC.EVENT_PROPERTY_OBSCURED, wasObscured(event));
}

if (proxy != null && proxy.hierarchyHasListener(TiC.EVENT_PINCH)) {
Expand Down
30 changes: 30 additions & 0 deletions apidoc/Titanium/UI/Button.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,36 @@ excludes:
properties: [children]
methods: [add, remove, removeAllChildren, replaceAt]
since: "0.8"

events:
- name: touchfiltered
summary: Fired when the device detects a swipe gesture against the view.
description: |
If the button's [filterTouchesWhenObscured](Titanium.UI.View.filterTouchesWhenObscured) property
is set `true`, then this event will be fired if the touch event was discarded because another app's
overlapping window obscured it.

This is a security feature to protect an app from "tapjacking", where a malicious app can use a
system overlay to intercept touch events in your app or to trick the end-user to tap on UI
in your app intended for the overlay.

You can use this event to display an alert dialog explaining why the button's action has been disabled.
Especially if the overlapping window is completely invisible.
platforms: [android]
since: "9.3.0"
properties:
- name: x
summary: X coordinate of the event from the `source` view's coordinate system.
type: Number

- name: y
summary: Y coordinate of the event from the `source` view's coordinate system.
type: Number

- name: obscured
type: Boolean
summary: Always `true` since the touch event passed through another app's overlapping window.

properties:
- name: attributedString
summary: Specify an attributed string for the label.
Expand Down