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 }