mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 06:50:54 +07:00
[platform] more stable machine ID detection methods and saner API
(cherry-picked from commit 5e1a84a75324da0e123a443132aa38ab70c2fdea) IJ-CR-147524 GitOrigin-RevId: 4d8a0428aa62ab6503940be1e74c47071707e81b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d4672f0d2e
commit
cf2c0980d5
@@ -50,11 +50,9 @@ class ABExperiment {
|
||||
companion object {
|
||||
private val AB_EXPERIMENTAL_OPTION_EP = ExtensionPointName<ABExperimentOptionBean>("com.intellij.experiment.abExperimentOption")
|
||||
private val LOG = logger<ABExperiment>()
|
||||
|
||||
private const val DEVICE_ID_PURPOSE = "A/B Experiment"
|
||||
private val DEVICE_ID_PURPOSE = "A/B Experiment" + ApplicationInfo.getInstance().shortVersion
|
||||
private const val TOTAL_NUMBER_OF_BUCKETS = 1024
|
||||
internal const val TOTAL_NUMBER_OF_GROUPS = 256
|
||||
private val DEVICE_ID_SALT = ApplicationInfo.getInstance().shortVersion
|
||||
|
||||
internal val OPTION_ID_FREE_GROUP = ABExperimentOptionId("free.option")
|
||||
|
||||
@@ -129,11 +127,11 @@ class ABExperiment {
|
||||
|
||||
private fun getUserBucketNumber(): Int {
|
||||
val deviceId = LOG.runAndLogException {
|
||||
MachineIdManager.getAnonymizedMachineId(DEVICE_ID_PURPOSE, DEVICE_ID_SALT)
|
||||
MachineIdManager.getAnonymizedMachineId(DEVICE_ID_PURPOSE)
|
||||
}
|
||||
|
||||
val bucketNumber = MathUtil.nonNegativeAbs(deviceId.hashCode()) % TOTAL_NUMBER_OF_BUCKETS
|
||||
LOG.debug { "User bucket number is: $bucketNumber." }
|
||||
return bucketNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.ide.plugins
|
||||
|
||||
import com.intellij.ide.IdeBundle
|
||||
@@ -191,7 +191,7 @@ open class StandalonePluginUpdateChecker(
|
||||
"https://plugins.jetbrains.com/plugins/list?pluginId=$pluginId&build=$buildNumber&pluginVersion=$currentVersion&os=$os&uuid=$uid"
|
||||
|
||||
if (!PropertiesComponent.getInstance().getBoolean(UpdateChecker.MACHINE_ID_DISABLED_PROPERTY, false)) {
|
||||
val machineId = MachineIdManager.getAnonymizedMachineId("JetBrainsUpdates", "")
|
||||
val machineId = MachineIdManager.getAnonymizedMachineId("JetBrainsUpdates")
|
||||
if (machineId != null) {
|
||||
url += "&${UpdateChecker.MACHINE_ID_PARAMETER}=$machineId"
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import static com.intellij.openapi.util.NullableLazyValue.lazyNullable;
|
||||
@ApiStatus.Internal
|
||||
public final class UpdateRequestParameters {
|
||||
private static final NullableLazyValue<String> ourMachineId =
|
||||
lazyNullable(() -> MachineIdManager.INSTANCE.getAnonymizedMachineId("JetBrainsUpdates", ""));
|
||||
lazyNullable(() -> MachineIdManager.INSTANCE.getAnonymizedMachineId("JetBrainsUpdates"));
|
||||
|
||||
public static @NotNull Url amendUpdateRequest(@NotNull Url url) {
|
||||
var parameters = new LinkedHashMap<String, String>();
|
||||
|
||||
@@ -31,5 +31,7 @@
|
||||
<orderEntry type="library" name="jackson-module-kotlin" level="project" />
|
||||
<orderEntry type="library" name="kotlin-reflect" level="project" />
|
||||
<orderEntry type="library" name="kotlinx-coroutines-core" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="JUnit5" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="assertJ" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -195,7 +195,7 @@ class EventLogRecorderConfiguration internal constructor(private val recorderId:
|
||||
return MachineId.DISABLED
|
||||
}
|
||||
val revision = if (value >= 0) value else DEFAULT_ID_REVISION
|
||||
val machineId = MachineIdManager.getAnonymizedMachineId("JetBrains${alternativeRecorderId ?: recorderId}", salt) ?: return MachineId.UNKNOWN
|
||||
val machineId = MachineIdManager.getAnonymizedMachineId("JetBrains${alternativeRecorderId ?: recorderId}${salt}") ?: return MachineId.UNKNOWN
|
||||
return MachineId(machineId, revision)
|
||||
}
|
||||
|
||||
@@ -279,4 +279,4 @@ private class AnonymizedIdsCache {
|
||||
fun computeIfAbsent(data: String, mappingFunction: (String) -> String): String {
|
||||
return cache.get(data, mappingFunction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ class IJFUSMapper: ApplicationUsagesCollector() {
|
||||
val mlConfig = EventLogConfigOptionsService.getInstance().getOptions(ML_RECORDER)
|
||||
val fusConfig = EventLogConfigOptionsService.getInstance().getOptions(FUS_RECORDER)
|
||||
return setOf(report.metric(
|
||||
MachineIdManager.getAnonymizedMachineId("JetBrains$ML_RECORDER", mlConfig.machineIdSalt ?: ""),
|
||||
MachineIdManager.getAnonymizedMachineId("JetBrains$FUS_RECORDER", fusConfig.machineIdSalt ?: ""),
|
||||
MachineIdManager.getAnonymizedMachineId("JetBrains${ML_RECORDER}${mlConfig.machineIdSalt}" ?: ""),
|
||||
MachineIdManager.getAnonymizedMachineId("JetBrains${FUS_RECORDER}${fusConfig.machineIdSalt}" ?: ""),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,88 +1,77 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.internal.statistic.eventLog.fus
|
||||
|
||||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.execution.util.ExecUtil
|
||||
import com.intellij.internal.statistic.eventLog.EventLogConfiguration
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.sun.jna.platform.mac.IOKitUtil
|
||||
import com.sun.jna.platform.win32.Advapi32Util
|
||||
import com.sun.jna.platform.win32.COM.WbemcliUtil
|
||||
import com.sun.jna.platform.win32.Ole32
|
||||
import com.sun.jna.platform.win32.WinReg
|
||||
import java.io.IOException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.util.regex.Pattern
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.readText
|
||||
|
||||
object MachineIdManager {
|
||||
private const val IOREG_COMMAND_TIMEOUT_MS = 2000
|
||||
private val macMachineIdPattern = Pattern.compile("\"IOPlatformUUID\"\\s*=\\s*\"(?<machineId>.*)\"")
|
||||
private val linuxMachineIdPaths = listOf("/etc/machine-id", "/var/lib/dbus/machine-id")
|
||||
private val LOG = logger<MachineIdManager>()
|
||||
|
||||
@Deprecated("Use `getAnonymizedMachineId(String)`", level = DeprecationLevel.ERROR)
|
||||
fun getAnonymizedMachineId(purpose: String, salt: String): String? = getAnonymizedMachineId(purpose + salt)
|
||||
|
||||
/**
|
||||
* @param purpose What id will be used for, shouldn't be empty.
|
||||
* @return Anonymized machine id or null If getting machine id was failed.
|
||||
* @param purpose what the ID will be used for; must not be empty.
|
||||
* @return anonymized machine ID, or `null` if getting a machine ID has failed.
|
||||
*/
|
||||
fun getAnonymizedMachineId(purpose: String, salt: String): String? {
|
||||
if (purpose.isEmpty()) {
|
||||
throw IllegalArgumentException("Argument [purpose] should not be empty.")
|
||||
fun getAnonymizedMachineId(purpose: String): String? {
|
||||
require (purpose.isNotEmpty()) { "`purpose` should not be empty" }
|
||||
return machineId.value?.let { machineId ->
|
||||
EventLogConfiguration.hashSha256((System.getProperty("user.name") + purpose).toByteArray(), machineId)
|
||||
}
|
||||
val machineId = getMachineId() ?: return null
|
||||
return EventLogConfiguration.hashSha256((System.getProperty("user.name") + purpose + salt).toByteArray(), machineId)
|
||||
}
|
||||
|
||||
private fun getMachineId(): String? {
|
||||
return try {
|
||||
private val machineId: Lazy<String?> = lazy {
|
||||
runCatching {
|
||||
when {
|
||||
SystemInfo.isWindows -> {
|
||||
Advapi32Util.registryGetStringValue(WinReg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Cryptography", "MachineGuid")
|
||||
}
|
||||
SystemInfo.isMac -> {
|
||||
getMacOsMachineId()
|
||||
}
|
||||
SystemInfo.isLinux -> {
|
||||
getLinuxMachineId()
|
||||
}
|
||||
SystemInfo.isWindows -> getWindowsMachineId()
|
||||
SystemInfo.isMac -> getMacOsMachineId()
|
||||
SystemInfo.isLinux -> getLinuxMachineId()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
null
|
||||
}
|
||||
}.onFailure { LOG.debug(it) }.getOrNull()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads machineId from /etc/machine-id or if not found from /var/lib/dbus/machine-id
|
||||
*
|
||||
* See https://manpages.debian.org/testing/systemd/machine-id.5.en.html for more details
|
||||
*/
|
||||
private fun getLinuxMachineId(): String? {
|
||||
for (machineIdPath in linuxMachineIdPaths) {
|
||||
try {
|
||||
val machineId = Files.readString(Paths.get(machineIdPath))
|
||||
if (!machineId.isNullOrEmpty()) {
|
||||
return machineId.trim()
|
||||
/** See [Win32_ComputerSystemProduct](https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-computersystemproduct). */
|
||||
private fun getWindowsMachineId(): String? =
|
||||
runCatching {
|
||||
Ole32.INSTANCE.CoInitializeEx(null, Ole32.COINIT_APARTMENTTHREADED)
|
||||
WbemcliUtil.WmiQuery("Win32_ComputerSystemProduct", ComputerSystemProductProperty::class.java)
|
||||
.execute(2000)
|
||||
.let { result ->
|
||||
if (result.resultCount > 0) result.getValue(ComputerSystemProductProperty.UUID, 0).toString()
|
||||
else null
|
||||
}
|
||||
}
|
||||
catch (ignore: IOException) {
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}.recover {
|
||||
LOG.debug(it)
|
||||
Advapi32Util.registryGetStringValue(WinReg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Cryptography", "MachineGuid")
|
||||
}.getOrThrow()
|
||||
|
||||
enum class ComputerSystemProductProperty { UUID }
|
||||
|
||||
/**
|
||||
* Invokes `ioreg -rd1 -c IOPlatformExpertDevice` to get IOPlatformUUID
|
||||
*/
|
||||
private fun getMacOsMachineId(): String? {
|
||||
val commandLine = GeneralCommandLine("ioreg", "-rd1", "-c", "IOPlatformExpertDevice")
|
||||
val processOutput = ExecUtil.execAndGetOutput(commandLine, IOREG_COMMAND_TIMEOUT_MS)
|
||||
if (processOutput.exitCode == 0) {
|
||||
val matcher = macMachineIdPattern.matcher(StringUtil.newBombedCharSequence(processOutput.stdout, 1000))
|
||||
if (matcher.find()) {
|
||||
return matcher.group("machineId")
|
||||
}
|
||||
/** See [IOPlatformExpertDevice](https://developer.apple.com/documentation/kernel/ioplatformexpertdevice). */
|
||||
private fun getMacOsMachineId(): String? =
|
||||
IOKitUtil.getMatchingService("IOPlatformExpertDevice")?.let { device ->
|
||||
val property = device.getStringProperty("IOPlatformUUID")
|
||||
device.release()
|
||||
property
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/** See [MACHINE-ID(5)](https://manpages.debian.org/testing/systemd/machine-id.5.en.html). */
|
||||
private fun getLinuxMachineId(): String? =
|
||||
sequenceOf("/etc/machine-id", "/var/lib/dbus/machine-id")
|
||||
.map {
|
||||
runCatching { Path.of(it).readText().trim().takeIf(String::isNotEmpty) }
|
||||
.onFailure { LOG.debug(it) }
|
||||
.getOrNull()
|
||||
}
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.internal.statistic
|
||||
|
||||
import com.intellij.internal.statistic.eventLog.fus.MachineIdManager
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Assertions.assertThrows
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class MachineIdManagerTest {
|
||||
@Test fun smoke() {
|
||||
assertThat(MachineIdManager.getAnonymizedMachineId("test"))
|
||||
.isNotNull
|
||||
}
|
||||
|
||||
@Test fun contract() {
|
||||
assertThrows(IllegalArgumentException::class.java) {
|
||||
MachineIdManager.getAnonymizedMachineId("")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ class IdService : PersistentStateComponentWithModificationTracker<IdService.Stat
|
||||
"undefined"
|
||||
}
|
||||
|
||||
val machineId by lazy { MachineIdManager.getAnonymizedMachineId("com.intellij.platform.ae.database", "salty") ?: "undefined" }
|
||||
val machineId by lazy { MachineIdManager.getAnonymizedMachineId("com.intellij.platform.ae.database") ?: "undefined" }
|
||||
|
||||
fun getDatabaseId(metadata: SqliteDatabaseMetadata) = metadata.ideId
|
||||
|
||||
@@ -54,4 +54,4 @@ class IdService : PersistentStateComponentWithModificationTracker<IdService.Stat
|
||||
override fun noStateLoaded() {
|
||||
myState.id = UUID.randomUUID().toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user