Unverified Commit 788f15f4 authored by Valere's avatar Valere Committed by GitHub

Merge pull request #3393 from vector-im/feature/jitsi_perm

Jitsi Widget Permissions
parents f68c32fe 80b197e9
......@@ -6,9 +6,10 @@ MatrixSdk 🚀:
Features ✨:
- Privacy / Room Widget permissions (#3378)
- Privacy / Widget Permission for jitsi widgets (#3391)
Improvementss 🙌:
-
- Jitsi / Use mx display name in Jitsi conf
Other changes:
- Add User-Interactive Auth to /account/3pid/add (#3333)
......
......@@ -259,7 +259,7 @@ class IntegrationManagerActivity : AbstractWidgetActivity() {
Log.d(LOG_TAG, "Received request to get widget in room " + mRoom!!.roomId)
val widgets = widgetManager.getActiveWidgets(mSession, mRoom)
val widgets = WidgetsManager.getActiveWidgets(mSession, mRoom)
val responseData = ArrayList<JsonDict<Any>>()
for (widget in widgets) {
......
......@@ -31,12 +31,14 @@ import org.jetbrains.annotations.Nullable;
import org.jitsi.meet.sdk.JitsiMeetActivityDelegate;
import org.jitsi.meet.sdk.JitsiMeetActivityInterface;
import org.jitsi.meet.sdk.JitsiMeetConferenceOptions;
import org.jitsi.meet.sdk.JitsiMeetUserInfo;
import org.jitsi.meet.sdk.JitsiMeetView;
import org.jitsi.meet.sdk.JitsiMeetViewListener;
import org.matrix.androidsdk.MXSession;
import org.matrix.androidsdk.core.Log;
import org.matrix.androidsdk.data.Room;
import java.net.URL;
import java.util.Map;
import butterknife.BindView;
......@@ -65,7 +67,7 @@ public class JitsiCallActivity extends VectorAppCompatActivity implements JitsiM
/**
* Base server URL
*/
private static final String JITSI_SERVER_URL = "https://jitsi.riot.im/";
public static final String JITSI_SERVER_URL = "https://jitsi.riot.im/";
// the jitsi view
private JitsiMeetView mJitsiView;
......@@ -134,11 +136,6 @@ public class JitsiCallActivity extends VectorAppCompatActivity implements JitsiM
return;
}
if (getWidgetManager() == null) {
finish();
return;
}
mRoom = mSession.getDataHandler().getRoom(mWidget.getRoomId());
if (null == mRoom) {
Log.e(LOG_TAG, "## onCreate() : undefined room " + mWidget.getRoomId());
......@@ -156,8 +153,22 @@ public class JitsiCallActivity extends VectorAppCompatActivity implements JitsiM
*/
private void loadURL() {
try {
JitsiMeetUserInfo userInfo = new JitsiMeetUserInfo();
userInfo.setDisplayName(mSession.getMyUser().displayname);
try {
String avatarUrl = mSession.getMyUser().avatar_url;
if (avatarUrl != null) {
String downloadableUrl = mSession.getContentManager().getDownloadableUrl(avatarUrl, false);
if (downloadableUrl != null) {
userInfo.setAvatar(new URL(downloadableUrl));
}
}
} catch (Exception e) {
//nop
}
JitsiMeetConferenceOptions jitsiMeetConferenceOptions = new JitsiMeetConferenceOptions.Builder()
.setVideoMuted(!mIsVideoCall)
.setUserInfo(userInfo)
// Configure the title of the screen
// TODO config.putString("callDisplayName", mRoom.getRoomDisplayName(this));
.setRoom(mCallUrl)
......
......@@ -118,6 +118,7 @@ import im.vector.features.hhs.ResourceLimitEventListener;
import im.vector.fragments.VectorMessageListFragment;
import im.vector.fragments.VectorReadReceiptsDialogFragment;
import im.vector.fragments.VectorUnknownDevicesFragment;
import im.vector.fragments.roomwidgets.RoomWidgetPermissionBottomSheet;
import im.vector.listeners.IMessagesAdapterActionsListener;
import im.vector.ui.themes.ThemeUtils;
import im.vector.util.CallsManager;
......@@ -128,6 +129,7 @@ import im.vector.util.PreferencesManager;
import im.vector.util.ReadMarkerManager;
import im.vector.util.RoomUtils;
import im.vector.util.SlashCommandsParser;
import im.vector.util.UrlUtilKt;
import im.vector.util.VectorMarkdownParser;
import im.vector.util.VectorRoomMediasSender;
import im.vector.util.VectorUtils;
......@@ -138,6 +140,7 @@ import im.vector.view.VectorOngoingConferenceCallView;
import im.vector.view.VectorPendingCallView;
import im.vector.widgets.Widget;
import im.vector.widgets.WidgetsManager;
import kotlin.Unit;
/**
* Displays a single room with messages.
......@@ -924,7 +927,7 @@ public class VectorRoomActivity extends MXCActionBarActivity implements
}
new AlertDialog.Builder(VectorRoomActivity.this)
.setItems(widgetNames.toArray(CharSequences), new DialogInterface.OnClickListener() {
.setItems(widgetNames.toArray(CharSequences), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface d, int n) {
d.cancel();
......@@ -1765,10 +1768,31 @@ public class VectorRoomActivity extends MXCActionBarActivity implements
.setPositiveButton(R.string.ok, null)
.show();
} else {
final Intent intent = new Intent(this, JitsiCallActivity.class);
intent.putExtra(JitsiCallActivity.EXTRA_WIDGET_ID, widget);
intent.putExtra(JitsiCallActivity.EXTRA_ENABLE_VIDEO, aIsVideoCall);
startActivity(intent);
//Here check native widget perm
String domain = UrlUtilKt.extractDomain(JitsiCallActivity.JITSI_SERVER_URL);
if (domain == null) return; //display a toast?
boolean isAllowed = mSession.getIntegrationManager().isNativeWidgetAllowed("jitsi", domain);
if (isAllowed) {
final Intent intent = new Intent(this, JitsiCallActivity.class);
intent.putExtra(JitsiCallActivity.EXTRA_WIDGET_ID, widget);
intent.putExtra(JitsiCallActivity.EXTRA_ENABLE_VIDEO, aIsVideoCall);
startActivity(intent);
} else {
//we need to prompt for permissions
RoomWidgetPermissionBottomSheet bs = RoomWidgetPermissionBottomSheet.Companion
.newInstance(mSession.getMyUserId(), widget);
bs.setOnFinish((accepted) -> {
if (accepted) {
final Intent intent = new Intent(this, JitsiCallActivity.class);
intent.putExtra(JitsiCallActivity.EXTRA_WIDGET_ID, widget);
intent.putExtra(JitsiCallActivity.EXTRA_ENABLE_VIDEO, aIsVideoCall);
startActivity(intent);
}
return Unit.INSTANCE;
});
bs.show(getSupportFragmentManager(), "JitsiPerm");
}
}
}
......
......@@ -65,7 +65,11 @@ class WidgetActivity : VectorAppCompatActivity() {
//already there
} else {
RoomWidgetPermissionBottomSheet
.newInstance(viewModel.session!!.myUserId, viewModel.widget)
.newInstance(viewModel.session!!.myUserId, viewModel.widget).apply {
onFinish = { accepted ->
if (!accepted) finish()
}
}
.show(supportFragmentManager, FRAGMENT_TAG_PERMISSION)
}
}
......
......@@ -635,9 +635,9 @@ class VectorSettingsPreferencesFragment : PreferenceFragmentCompat(), SharedPref
//Disable it while updating the state, will be re-enabled by the account data listener.
it.isEnabled = false
mSession.enableIntegrationManagerUsage(newValue as Boolean, object : ApiCallback<Void> {
mSession.integrationManager.enableIntegrationManagerUsage(newValue as Boolean, object : ApiCallback<Void> {
override fun onSuccess(info: Void?) {
//nop
refreshIntegrationManagerSettings()
}
override fun onUnexpectedError(e: java.lang.Exception?) {
......@@ -2038,7 +2038,7 @@ class VectorSettingsPreferencesFragment : PreferenceFragmentCompat(), SharedPref
mSession.identityServerManager.checkAdd3pidInteractiveFlow(listOf(LoginRestClient.LOGIN_FLOW_TYPE_PASSWORD),
object : ApiCallback<IdentityServerManager.SupportedFlowResult> {
override fun onSuccess(info: IdentityServerManager.SupportedFlowResult) {
addEmailBtn.isEnabled = info == IdentityServerManager.SupportedFlowResult.SUPPORTED
addEmailBtn.isEnabled = info == IdentityServerManager.SupportedFlowResult.SUPPORTED
|| info == IdentityServerManager.SupportedFlowResult.INTERACTIVE_AUTH_NOT_SUPPORTED
}
......
......@@ -26,7 +26,6 @@ import android.widget.TextView
import butterknife.BindView
import butterknife.OnClick
import com.airbnb.mvrx.MvRx
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.R
......@@ -53,8 +52,8 @@ class RoomWidgetPermissionBottomSheet : VectorBaseBottomSheetDialogFragment() {
@BindView(R.id.bottom_sheet_widget_permission_owner_avatar)
lateinit var authorAvatarView: ImageView
private val sharedActivityViewModel: RoomWidgetViewModel by activityViewModel()
var onFinish: ((Boolean) -> Unit)? = null
override fun invalidate() = withState(viewModel) { state ->
......@@ -63,9 +62,13 @@ class RoomWidgetPermissionBottomSheet : VectorBaseBottomSheetDialogFragment() {
VectorUtils.loadUserAvatar(requireContext(), viewModel.session, authorAvatarView,
state.authorAvatarUrl, state.authorId, state.authorName)
val domain = state.widgetDomain ?: ""
val infoBuilder = SpannableStringBuilder()
.append(getString(R.string.room_widget_permission_shared_info_title, "'${state.widgetDomain
?: ""}'"))
.append(getString(
R.string.room_widget_permission_webview_shared_info_title
.takeIf { state.isWebviewWidget }
?: R.string.room_widget_permission_shared_info_title,
"'$domain'"))
infoBuilder.append("\n")
state.permissionsList?.forEach {
......@@ -94,19 +97,21 @@ class RoomWidgetPermissionBottomSheet : VectorBaseBottomSheetDialogFragment() {
viewModel.blockWidget()
//optimistic dismiss
dismiss()
sharedActivityViewModel.doFinish()
onFinish?.invoke(false)
}
@OnClick(R.id.bottom_sheet_widget_permission_continue_button)
fun doAccept() {
viewModel.allowWidget()
onFinish?.invoke(true)
//optimistic dismiss
dismiss()
}
override fun onCancel(dialog: DialogInterface?) {
super.onCancel(dialog)
sharedActivityViewModel.doFinish()
onFinish?.invoke(false)
}
@Parcelize
......
......@@ -21,17 +21,19 @@ import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import im.vector.Matrix
import im.vector.R
import im.vector.activity.JitsiCallActivity
import im.vector.util.extractDomain
import im.vector.widgets.Widget
import im.vector.widgets.WidgetsManager
import org.matrix.androidsdk.MXSession
import org.matrix.androidsdk.core.Log
import org.matrix.androidsdk.core.callback.ApiCallback
import org.matrix.androidsdk.core.model.MatrixError
import org.matrix.androidsdk.core.callback.SimpleApiCallback
import java.net.URL
data class RoomWidgetPermissionViewState(
val authorId: String = "",
val authorAvatarUrl: String? = null,
val authorName: String? = null,
val isWebviewWidget: Boolean = true,
val widgetDomain: String? = null,
//List of @StringRes
val permissionsList: List<Int>? = null
......@@ -40,49 +42,48 @@ data class RoomWidgetPermissionViewState(
class RoomWidgetPermissionViewModel(val session: MXSession, val widget: Widget, initialState: RoomWidgetPermissionViewState)
: BaseMvRxViewModel<RoomWidgetPermissionViewState>(initialState, false) {
fun allowWidget(onFinished: (() -> Unit)? = null) {
session.integrationManager.setWidgetAllowed(widget.widgetEvent?.eventId
?: "", true, object : ApiCallback<Void?> {
fun allowWidget(onFinished: (() -> Unit)? = null) = withState { state ->
override fun onSuccess(info: Void?) {
onFinished?.invoke()
}
if (state.isWebviewWidget) {
session.integrationManager.setWidgetAllowed(widget.widgetEvent?.eventId
?: "", true, object : SimpleApiCallback<Void?>() {
override fun onUnexpectedError(e: Exception) {
Log.e(LOG_TAG, e.message)
}
override fun onSuccess(info: Void?) {
onFinished?.invoke()
}
override fun onNetworkError(e: Exception) {
Log.e(LOG_TAG, e.message)
}
})
} else {
session.integrationManager.setNativeWidgetDomainAllowed("jitsi", state.widgetDomain
?: "", true, object : SimpleApiCallback<Void?>() {
override fun onMatrixError(e: MatrixError) {
Log.e(LOG_TAG, e.message)
}
override fun onSuccess(info: Void?) {
onFinished?.invoke()
}
})
})
}
}
fun blockWidget(onFinished: (() -> Unit)? = null) {
session.integrationManager.setWidgetAllowed(widget.widgetEvent?.eventId
?: "", false, object : ApiCallback<Void?> {
override fun onSuccess(info: Void?) {
onFinished?.invoke()
}
fun blockWidget(onFinished: (() -> Unit)? = null) = withState { state ->
if (state.isWebviewWidget) {
session.integrationManager.setWidgetAllowed(widget.widgetEvent?.eventId
?: "", false, object : SimpleApiCallback<Void?>() {
override fun onSuccess(info: Void?) {
onFinished?.invoke()
}
override fun onUnexpectedError(e: Exception) {
Log.e(LOG_TAG, e.message)
}
})
} else {
session.integrationManager.setNativeWidgetDomainAllowed("jitsi", state.widgetDomain
?: "", false, object : SimpleApiCallback<Void?>() {
override fun onNetworkError(e: Exception) {
Log.e(LOG_TAG, e.message)
}
override fun onMatrixError(e: MatrixError) {
Log.e(LOG_TAG, e.message)
}
override fun onSuccess(info: Void?) {
onFinished?.invoke()
}
})
})
}
}
companion object : MvRxViewModelFactory<RoomWidgetPermissionViewModel, RoomWidgetPermissionViewState> {
......@@ -110,23 +111,41 @@ class RoomWidgetPermissionViewModel(val session: MXSession, val widget: Widget,
domain = null
}
//TODO check from widget urls the perms that should be shown?
//For now put all
val infoShared = listOf<Int>(
R.string.room_widget_permission_display_name,
R.string.room_widget_permission_avatar_url,
R.string.room_widget_permission_user_id,
R.string.room_widget_permission_theme,
R.string.room_widget_permission_widget_id,
R.string.room_widget_permission_room_id
)
return RoomWidgetPermissionViewState(
authorName = creator?.displayname,
authorId = widget.widgetEvent.sender,
authorAvatarUrl = creator?.getAvatarUrl(),
widgetDomain = domain,
permissionsList = infoShared
)
if (WidgetsManager.isJitsiWidget(widget)) {
val infoShared = listOf<Int>(
R.string.room_widget_permission_display_name,
R.string.room_widget_permission_avatar_url
)
return RoomWidgetPermissionViewState(
isWebviewWidget = false,
authorName = creator?.displayname,
authorId = widget.widgetEvent.sender,
authorAvatarUrl = creator?.getAvatarUrl(),
widgetDomain = extractDomain(JitsiCallActivity.JITSI_SERVER_URL),
permissionsList = infoShared
)
} else {
//TODO check from widget urls the perms that should be shown?
//For now put all
val infoShared = listOf<Int>(
R.string.room_widget_permission_display_name,
R.string.room_widget_permission_avatar_url,
R.string.room_widget_permission_user_id,
R.string.room_widget_permission_theme,
R.string.room_widget_permission_widget_id,
R.string.room_widget_permission_room_id
)
return RoomWidgetPermissionViewState(
authorName = creator?.displayname,
authorId = widget.widgetEvent.sender,
authorAvatarUrl = creator?.getAvatarUrl(),
widgetDomain = domain,
permissionsList = infoShared
)
}
}
}
}
\ No newline at end of file
......@@ -165,9 +165,7 @@ class RoomWidgetViewModel(initialState: RoomWidgetViewModelState, val widget: Wi
val isAllowed = session
?.integrationManager
?.getKnownWidgetPermissions()
?.find { it.stateEventId == widget.widgetEvent.eventId }
?.allowed
?.isWidgetAllowed( widget.widgetEvent.eventId)
?: false
if (!isAllowed) {
......
......@@ -16,6 +16,9 @@
package im.vector.util
import java.net.MalformedURLException
import java.net.URL
/**
* Schemes
*/
......@@ -41,4 +44,12 @@ fun removeUrlScheme(aUrl: String?): String? {
}
return urlRetValue
}
fun extractDomain(aUrl: String?): String? {
try {
return aUrl?.let { URL(it).host }
} catch (e : MalformedURLException) {
return null
}
}
\ No newline at end of file
......@@ -171,12 +171,8 @@ public class ActiveWidgetsBanner extends FrameLayout {
* Refresh the view visibility
*/
private void refresh() {
WidgetsManager wm = getWidgetManager(getContext());
if (wm == null) {
return;
}
if ((null != mRoom) && (null != mSession)) {
List<Widget> activeWidgets = wm.getActiveWebviewWidgets(mSession, mRoom);
List<Widget> activeWidgets = WidgetsManager.getActiveWebviewWidgets(mSession, mRoom);
Widget firstWidget = null;
if ((activeWidgets.size() != mActiveWidgets.size()) || !mActiveWidgets.containsAll(activeWidgets)) {
......@@ -202,7 +198,7 @@ public class ActiveWidgetsBanner extends FrameLayout {
setVisibility((mActiveWidgets.size() > 0) ? View.VISIBLE : View.GONE);
// show the close widget button if the user is allowed to do it
mCloseWidgetIcon.setVisibility(((null != firstWidget) && (null == wm.checkWidgetPermission(mSession, mRoom))) ?
mCloseWidgetIcon.setVisibility(((null != firstWidget) && (null == WidgetsManager.checkWidgetPermission(mSession, mRoom))) ?
View.VISIBLE : View.GONE);
}
}
......
......@@ -243,12 +243,8 @@ public class VectorOngoingConferenceCallView extends RelativeLayout {
* Refresh the view visibility
*/
public void refresh() {
WidgetsManager wm = Matrix.getWidgetManager(getContext());
if (wm == null) {
return;
}
if ((null != mRoom) && (null != mSession)) {
List<Widget> mActiveWidgets = wm.getActiveJitsiWidgets(mSession, mRoom);
List<Widget> mActiveWidgets = WidgetsManager.getActiveJitsiWidgets(mSession, mRoom);
Widget widget = mActiveWidgets.isEmpty() ? null : mActiveWidgets.get(0);
if (mActiveWidget != widget) {
......@@ -266,7 +262,7 @@ public class VectorOngoingConferenceCallView extends RelativeLayout {
setVisibility(((!MXCallsManager.isCallInProgress(call) && mRoom.isOngoingConferenceCall()) || (null != mActiveWidget)) ? View.VISIBLE : View.GONE);
// show the close widget button if the user is allowed to do it
mCloseWidgetIcon.setVisibility(((null != mActiveWidget) && (null == wm.checkWidgetPermission(mSession, mRoom))) ?
mCloseWidgetIcon.setVisibility(((null != mActiveWidget) && (null == WidgetsManager.checkWidgetPermission(mSession, mRoom))) ?
View.VISIBLE : View.GONE);
}
}
......
......@@ -95,10 +95,26 @@ public class WidgetsManager {
* @param room the room to check.
* @return the active widgets list
*/
public List<Widget> getActiveWidgets(MXSession session, Room room) {
public static List<Widget> getActiveWidgets(MXSession session, Room room) {
return getActiveWidgets(session, room, null, null);
}
public static Boolean isJitsiWidget(Widget widget) {
Event widgetEvent = widget.getWidgetEvent();
if (widgetEvent == null) return false;
try {
JsonObject jsonObject = widgetEvent.getContentAsJsonObject();
if (jsonObject != null && jsonObject.has("type")) {
String widgetType = jsonObject.get("type").getAsString();
return WIDGET_TYPE_JITSI.equals(widgetType);
}
} catch (Exception e) {
Log.e(LOG_TAG, "## getWidgets() failed : " + e.getMessage(), e);
}
return false;
}
/**
* List all active widgets in a room.
*
......@@ -108,7 +124,7 @@ public class WidgetsManager {
* @param excludedTypes the excluded widget types
* @return the active widgets list
*/
private List<Widget> getActiveWidgets(final MXSession session, final Room room, final Set<String> widgetTypes, final Set<String> excludedTypes) {
private static List<Widget> getActiveWidgets(final MXSession session, final Room room, final Set<String> widgetTypes, final Set<String> excludedTypes) {
// Get all im.vector.modular.widgets state events in the room
List<Event> widgetEvents = room.getState().getStateEvents(new HashSet<>(Arrays.asList(WIDGET_EVENT_TYPE)));
......@@ -194,7 +210,7 @@ public class WidgetsManager {
* @param room the room
* @return the list of active widgets
*/
public List<Widget> getActiveJitsiWidgets(final MXSession session, final Room room) {
public static List<Widget> getActiveJitsiWidgets(final MXSession session, final Room room) {
return getActiveWidgets(session, room, new HashSet<>(Arrays.asList(WidgetsManager.WIDGET_TYPE_JITSI)), null);
}
......@@ -205,7 +221,7 @@ public class WidgetsManager {
* @param room the room
* @return the list of active widgets
*/
public List<Widget> getActiveWebviewWidgets(final MXSession session, final Room room) {
public static List<Widget> getActiveWebviewWidgets(final MXSession session, final Room room) {
return getActiveWidgets(session, room, null, new HashSet<>(Arrays.asList(WidgetsManager.WIDGET_TYPE_JITSI)));
}
......
......@@ -7,21 +7,21 @@
android:icon="@drawable/ic_widget_refresh"
android:iconTint="?attr/vctr_icon_tint_on_dark_action_bar_color"
android:title="@string/room_widget_reload"
app:showAsAction="ifRoom" />
app:showAsAction="never" />
<item
android:id="@+id/action_widget_open_ext"
android:icon="@drawable/ic_widget_external_link"
android:iconTint="?attr/vctr_icon_tint_on_dark_action_bar_color"
android:title="@string/room_widget_open_in_browser"
app:showAsAction="ifRoom" />
app:showAsAction="never" />
<item
android:id="@+id/action_close"
android:icon="@drawable/ic_widget_bin"
android:iconTint="@color/vector_error_color"
android:title="@string/delete"
app:showAsAction="ifRoom" />
app:showAsAction="never" />
<item
android:id="@+id/action_revoke"
......
......@@ -1118,7 +1118,8 @@
<string name="room_widget_activity_title">Widget</string>
<string name="room_widget_permission_title">Load Widget</string>
<string name="room_widget_permission_added_by">This widget was added by:</string>
<string name="room_widget_permission_shared_info_title">Using it may set cookies and share data with %s:</string>
<string name="room_widget_permission_webview_shared_info_title">Using it may set cookies and share data with %s:</string>
<string name="room_widget_permission_shared_info_title">Using it may share data with %s:</string>
<string name="room_widget_failed_to_load">Failed to load widget.\n%s</string>
<string name="room_widget_reload">Reload widget</string>
<string name="room_widget_open_in_browser">Open in browser</string>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment