File indexing completed on 2024-12-22 04:41:43

0001 /*
0002  * SPDX-FileCopyrightText: 2015 Vineet Garg <grg.vineet@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 package org.kde.kdeconnect;
0008 
0009 import static org.junit.Assert.assertEquals;
0010 import static org.junit.Assert.assertFalse;
0011 import static org.junit.Assert.assertNotNull;
0012 import static org.junit.Assert.assertTrue;
0013 import static org.mockito.ArgumentMatchers.any;
0014 import static org.mockito.ArgumentMatchers.anyInt;
0015 import static org.mockito.ArgumentMatchers.anyString;
0016 import static org.mockito.ArgumentMatchers.eq;
0017 
0018 import android.app.NotificationManager;
0019 import android.content.Context;
0020 import android.content.SharedPreferences;
0021 import android.preference.PreferenceManager;
0022 import android.util.Base64;
0023 import android.util.Log;
0024 
0025 import androidx.core.content.ContextCompat;
0026 
0027 import org.junit.Before;
0028 import org.junit.Test;
0029 import org.junit.runner.RunWith;
0030 import org.kde.kdeconnect.Backends.LanBackend.LanLink;
0031 import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
0032 import org.kde.kdeconnect.Helpers.DeviceHelper;
0033 import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
0034 import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
0035 import org.mockito.Mockito;
0036 import org.powermock.api.mockito.PowerMockito;
0037 import org.powermock.core.classloader.annotations.PrepareForTest;
0038 import org.powermock.modules.junit4.PowerMockRunner;
0039 
0040 import java.lang.reflect.InvocationTargetException;
0041 import java.lang.reflect.Method;
0042 import java.security.cert.Certificate;
0043 import java.security.cert.CertificateException;
0044 import java.util.Arrays;
0045 import java.util.HashSet;
0046 
0047 @RunWith(PowerMockRunner.class)
0048 @PrepareForTest({Base64.class, Log.class, PreferenceManager.class, ContextCompat.class})
0049 public class DeviceTest {
0050 
0051     private Context context;
0052 
0053     // Creating a paired device before each test case
0054     @Before
0055     public void setUp() {
0056         // Save new test device in settings
0057 
0058         String deviceId = "testDevice";
0059         String name = "Test Device";
0060         String encodedCertificate = "MIIDVzCCAj+gAwIBAgIBCjANBgkqhkiG9w0BAQUFADBVMS8wLQYDVQQDDCZfZGExNzlhOTFfZjA2\n" +
0061             "NF80NzhlX2JlOGNfMTkzNWQ3NTQ0ZDU0XzEMMAoGA1UECgwDS0RFMRQwEgYDVQQLDAtLZGUgY29u\n" +
0062             "bmVjdDAeFw0xNTA2MDMxMzE0MzhaFw0yNTA2MDMxMzE0MzhaMFUxLzAtBgNVBAMMJl9kYTE3OWE5\n" +
0063             "MV9mMDY0XzQ3OGVfYmU4Y18xOTM1ZDc1NDRkNTRfMQwwCgYDVQQKDANLREUxFDASBgNVBAsMC0tk\n" +
0064             "ZSBjb25uZWN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzH9GxS1lctpwYdSGAoPH\n" +
0065             "ws+MnVaL0PVDCuzrpxzXc+bChR87xofhQIesLPLZEcmUJ1MlEJ6jx4W+gVhvY2tUN7SoiKKbnq8s\n" +
0066             "WjI5ovs5yML3C1zPbOSJAdK613FcdkK+UGd/9dQk54gIozinC58iyTAChVVpB3pAF38EPxwKkuo2\n" +
0067             "qTzwk24d6PRxz1skkzwEphUQQzGboyHsAlJHN1MzM2/yFGB4l8iUua2d3ETyfy/xFEh/SwtGtXE5\n" +
0068             "KLz4cpb0fxjeYQZVruBKxzE07kgDO3zOhmP3LJ/KSPHWYImd1DWmpY9iDvoXr6+V7FAnRloaEIyg\n" +
0069             "7WwdlSCpo3TXVuIjLwIDAQABozIwMDAdBgNVHQ4EFgQUwmbHo8YbiR463GRKSLL3eIKyvDkwDwYD\n" +
0070             "VR0TAQH/BAUwAwIBADANBgkqhkiG9w0BAQUFAAOCAQEAydijH3rbnvpBDB/30w2PCGMT7O0N/XYM\n" +
0071             "wBtUidqa4NFumJrNrccx5Ehp4UP66BfP61HW8h2U/EekYfOsZyyWd4KnsDD6ycR8h/WvpK3BC2cn\n" +
0072             "I299wbqCEZmk5ZFFaEIDHdLAdgMCuxJkAzy9mMrWEa05Soxi2/ZXdrU9nXo5dzuPGYlirVPDHl7r\n" +
0073             "/urBxD6HVX3ObQJRJ7r/nAWyUVdX3/biJaDRsydftOpGU6Gi5c1JK4MWIz8Bsjh6mEjCsVatbPPl\n" +
0074             "yygGiJbDZfAvN2XoaVEBii2GDDCWfaFwPVPYlNTvjkUkMP8YThlMsiJ8Q4693XoLOL94GpNlCfUg\n" +
0075             "7n+KOQ==";
0076 
0077         this.context = Mockito.mock(Context.class);
0078 
0079         PowerMockito.mockStatic(Base64.class);
0080         PowerMockito.when(Base64.encodeToString(any(), anyInt())).thenAnswer(invocation -> java.util.Base64.getMimeEncoder().encodeToString((byte[]) invocation.getArguments()[0]));
0081         PowerMockito.when(Base64.decode(anyString(), anyInt())).thenAnswer(invocation -> java.util.Base64.getMimeDecoder().decode((String) invocation.getArguments()[0]));
0082 
0083         PowerMockito.mockStatic(Log.class);
0084 
0085         //Store device information needed to create a Device object in a future
0086         MockSharedPreference deviceSettings = new MockSharedPreference();
0087         SharedPreferences.Editor editor = deviceSettings.edit();
0088         editor.putString("deviceName", name);
0089         editor.putString("deviceType", DeviceType.PHONE.toString());
0090         editor.putString("certificate", encodedCertificate);
0091         editor.apply();
0092         Mockito.when(context.getSharedPreferences(eq(deviceId), eq(Context.MODE_PRIVATE))).thenReturn(deviceSettings);
0093 
0094         //Store the device as trusted
0095         MockSharedPreference trustedSettings = new MockSharedPreference();
0096         trustedSettings.edit().putBoolean(deviceId, true).apply();
0097         Mockito.when(context.getSharedPreferences(eq("trusted_devices"), eq(Context.MODE_PRIVATE))).thenReturn(trustedSettings);
0098 
0099         //Store an untrusted device
0100         MockSharedPreference untrustedSettings = new MockSharedPreference();
0101         Mockito.when(context.getSharedPreferences(eq("unpairedTestDevice"), eq(Context.MODE_PRIVATE))).thenReturn(untrustedSettings);
0102 
0103         //Default shared prefs, including our own private key
0104         PowerMockito.mockStatic(PreferenceManager.class);
0105         MockSharedPreference defaultSettings = new MockSharedPreference();
0106         PowerMockito.when(PreferenceManager.getDefaultSharedPreferences(any())).thenReturn(defaultSettings);
0107         RsaHelper.initialiseRsaKeys(context);
0108 
0109         PowerMockito.mockStatic(ContextCompat.class);
0110         PowerMockito.when(ContextCompat.getSystemService(context, NotificationManager.class)).thenReturn(Mockito.mock(NotificationManager.class));
0111     }
0112 
0113     @Test
0114     public void testDeviceInfoToIdentityPacket() throws CertificateException {
0115         String deviceId = "testDevice";
0116         SharedPreferences settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
0117         DeviceInfo di = DeviceInfo.loadFromSettings(context, deviceId,  settings);
0118         di.protocolVersion = DeviceHelper.ProtocolVersion;
0119         di.incomingCapabilities = new HashSet<>(Arrays.asList("kdeconnect.plugin1State", "kdeconnect.plugin2State"));
0120         di.outgoingCapabilities = new HashSet<>(Arrays.asList("kdeconnect.plugin1State.request", "kdeconnect.plugin2State.request"));
0121 
0122         NetworkPacket np = di.toIdentityPacket();
0123 
0124         assertEquals(di.id, np.getString("deviceId"));
0125         assertEquals(di.name, np.getString("deviceName"));
0126         assertEquals(di.protocolVersion, np.getInt("protocolVersion"));
0127         assertEquals(di.type.toString(), np.getString("deviceType"));
0128         assertEquals(di.incomingCapabilities, np.getStringSet("incomingCapabilities"));
0129         assertEquals(di.outgoingCapabilities, np.getStringSet("outgoingCapabilities"));
0130     }
0131 
0132     @Test
0133     public void testDeviceType() {
0134         assertEquals(DeviceType.PHONE, DeviceType.fromString(DeviceType.PHONE.toString()));
0135         assertEquals(DeviceType.TABLET, DeviceType.fromString(DeviceType.TABLET.toString()));
0136         assertEquals(DeviceType.DESKTOP, DeviceType.fromString(DeviceType.DESKTOP.toString()));
0137         assertEquals(DeviceType.LAPTOP, DeviceType.fromString(DeviceType.LAPTOP.toString()));
0138         assertEquals(DeviceType.TV, DeviceType.fromString(DeviceType.TV.toString()));
0139         assertEquals(DeviceType.DESKTOP, DeviceType.fromString("invalid"));
0140     }
0141 
0142     // Basic paired device testing
0143     @Test
0144     public void testDevice() throws CertificateException {
0145         Device device = new Device(context, "testDevice");
0146 
0147         assertEquals(device.getDeviceId(), "testDevice");
0148         assertEquals(device.getDeviceType(), DeviceType.PHONE);
0149         assertEquals(device.getName(), "Test Device");
0150         assertTrue(device.isPaired());
0151         assertNotNull(device.deviceInfo.certificate);
0152     }
0153 
0154     public void testPairingDone() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, CertificateException {
0155 
0156         NetworkPacket fakeNetworkPacket = new NetworkPacket(NetworkPacket.PACKET_TYPE_IDENTITY);
0157         String deviceId = "unpairedTestDevice";
0158         fakeNetworkPacket.set("deviceId", deviceId);
0159         fakeNetworkPacket.set("deviceName", "Unpaired Test Device");
0160         fakeNetworkPacket.set("protocolVersion", DeviceHelper.ProtocolVersion);
0161         fakeNetworkPacket.set("deviceType", DeviceType.PHONE.toString());
0162         String certificateString =
0163             "MIIDVzCCAj+gAwIBAgIBCjANBgkqhkiG9w0BAQUFADBVMS8wLQYDVQQDDCZfZGExNzlhOTFfZjA2\n" +
0164             "NF80NzhlX2JlOGNfMTkzNWQ3NTQ0ZDU0XzEMMAoGA1UECgwDS0RFMRQwEgYDVQQLDAtLZGUgY29u\n" +
0165             "bmVjdDAeFw0xNTA2MDMxMzE0MzhaFw0yNTA2MDMxMzE0MzhaMFUxLzAtBgNVBAMMJl9kYTE3OWE5\n" +
0166             "MV9mMDY0XzQ3OGVfYmU4Y18xOTM1ZDc1NDRkNTRfMQwwCgYDVQQKDANLREUxFDASBgNVBAsMC0tk\n" +
0167             "ZSBjb25uZWN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzH9GxS1lctpwYdSGAoPH\n" +
0168             "ws+MnVaL0PVDCuzrpxzXc+bChR87xofhQIesLPLZEcmUJ1MlEJ6jx4W+gVhvY2tUN7SoiKKbnq8s\n" +
0169             "WjI5ovs5yML3C1zPbOSJAdK613FcdkK+UGd/9dQk54gIozinC58iyTAChVVpB3pAF38EPxwKkuo2\n" +
0170             "qTzwk24d6PRxz1skkzwEphUQQzGboyHsAlJHN1MzM2/yFGB4l8iUua2d3ETyfy/xFEh/SwtGtXE5\n" +
0171             "KLz4cpb0fxjeYQZVruBKxzE07kgDO3zOhmP3LJ/KSPHWYImd1DWmpY9iDvoXr6+V7FAnRloaEIyg\n" +
0172             "7WwdlSCpo3TXVuIjLwIDAQABozIwMDAdBgNVHQ4EFgQUwmbHo8YbiR463GRKSLL3eIKyvDkwDwYD\n" +
0173             "VR0TAQH/BAUwAwIBADANBgkqhkiG9w0BAQUFAAOCAQEAydijH3rbnvpBDB/30w2PCGMT7O0N/XYM\n" +
0174             "wBtUidqa4NFumJrNrccx5Ehp4UP66BfP61HW8h2U/EekYfOsZyyWd4KnsDD6ycR8h/WvpK3BC2cn\n" +
0175             "I299wbqCEZmk5ZFFaEIDHdLAdgMCuxJkAzy9mMrWEa05Soxi2/ZXdrU9nXo5dzuPGYlirVPDHl7r\n" +
0176             "/urBxD6HVX3ObQJRJ7r/nAWyUVdX3/biJaDRsydftOpGU6Gi5c1JK4MWIz8Bsjh6mEjCsVatbPPl\n" +
0177             "yygGiJbDZfAvN2XoaVEBii2GDDCWfaFwPVPYlNTvjkUkMP8YThlMsiJ8Q4693XoLOL94GpNlCfUg\n" +
0178             "7n+KOQ==";
0179         byte[] certificateBytes = Base64.decode(certificateString, 0);
0180         Certificate certificate = SslHelper.parseCertificate(certificateBytes);
0181         DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(fakeNetworkPacket, certificate);
0182 
0183         LanLinkProvider linkProvider = Mockito.mock(LanLinkProvider.class);
0184         Mockito.when(linkProvider.getName()).thenReturn("LanLinkProvider");
0185         LanLink link = Mockito.mock(LanLink.class);
0186         Mockito.when(link.getLinkProvider()).thenReturn(linkProvider);
0187         Mockito.when(link.getDeviceId()).thenReturn(deviceId);
0188         Mockito.when(link.getDeviceInfo()).thenReturn(deviceInfo);
0189         Device device = new Device(context, link);
0190 
0191         assertNotNull(device);
0192         assertEquals(device.getDeviceId(), deviceId);
0193         assertEquals(device.getName(), "Unpaired Test Device");
0194         assertEquals(device.getDeviceType(), DeviceType.PHONE);
0195         assertNotNull(device.deviceInfo.certificate);
0196 
0197         Method method = PairingHandler.class.getDeclaredMethod("pairingDone");
0198         method.setAccessible(true);
0199         method.invoke(device.pairingHandler);
0200 
0201         assertTrue(device.isPaired());
0202 
0203         SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
0204         assertTrue(preferences.getBoolean(device.getDeviceId(), false));
0205 
0206         SharedPreferences settings = context.getSharedPreferences(device.getDeviceId(), Context.MODE_PRIVATE);
0207         assertEquals(settings.getString("deviceName", "Unknown device"), "Unpaired Test Device");
0208         assertEquals(settings.getString("deviceType", "tablet"), "phone");
0209 
0210         // Cleanup for unpaired test device
0211         preferences.edit().remove(device.getDeviceId()).apply();
0212         settings.edit().clear().apply();
0213     }
0214 
0215     @Test
0216     public void testUnpair() throws CertificateException {
0217         PairingHandler.PairingCallback pairingCallback = Mockito.mock(PairingHandler.PairingCallback.class);
0218         Device device = new Device(context, "testDevice");
0219         device.addPairingCallback(pairingCallback);
0220 
0221         device.unpair();
0222 
0223         assertFalse(device.isPaired());
0224 
0225         SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
0226         assertFalse(preferences.getBoolean(device.getDeviceId(), false));
0227 
0228         Mockito.verify(pairingCallback, Mockito.times(1)).unpaired();
0229     }
0230 }