diff --git a/src/main/kotlin/app/termora/LocalSecret.kt b/src/main/kotlin/app/termora/LocalSecret.kt deleted file mode 100644 index 5194918..0000000 --- a/src/main/kotlin/app/termora/LocalSecret.kt +++ /dev/null @@ -1,29 +0,0 @@ -package app.termora - -import org.apache.commons.codec.digest.DigestUtils -import org.apache.commons.lang3.StringUtils -import org.apache.commons.lang3.SystemUtils - -/** - * 用户需要保证自己的电脑是可信环境 - */ -internal class LocalSecret private constructor() { - - companion object { - fun getInstance(): LocalSecret { - return ApplicationScope.forApplicationScope() - .getOrCreate(LocalSecret::class) { LocalSecret() } - } - } - - /** - * 一个 16 长度的密码 - */ - val password: String = StringUtils.substring(DigestUtils.sha256Hex(SystemUtils.USER_NAME), 0, 16) - - /** - * 一个 12 长度的盐 - */ - val salt: String = StringUtils.substring(password, 0, 16) - -} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/account/LoginServerDialog.kt b/src/main/kotlin/app/termora/account/LoginServerDialog.kt index 5bbec44..0e06512 100644 --- a/src/main/kotlin/app/termora/account/LoginServerDialog.kt +++ b/src/main/kotlin/app/termora/account/LoginServerDialog.kt @@ -16,12 +16,13 @@ import org.jdesktop.swingx.JXHyperlink import java.awt.Component import java.awt.Dimension import java.awt.Window -import java.awt.event.ItemEvent import java.awt.event.WindowAdapter import java.awt.event.WindowEvent import java.net.URI import java.util.concurrent.atomic.AtomicBoolean import javax.swing.* +import javax.swing.event.ListDataEvent +import javax.swing.event.ListDataListener import kotlin.math.max import kotlin.time.Duration.Companion.milliseconds @@ -33,6 +34,10 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) { private val cancelAction = super.createCancelAction() private val cancelButton = super.createJButtonForAction(cancelAction) private val isLoggingIn = AtomicBoolean(false) + private val singaporeServer = + Server(I18n.getString("termora.settings.account.server-singapore"), "https://account.termora.app") + private val chinaServer = + Server(I18n.getString("termora.settings.account.server-china"), "https://account.termora.cn") init { isModal = true @@ -63,16 +68,12 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) { val step = 2 - val singaporeServer = - Server(I18n.getString("termora.settings.account.server-singapore"), "https://account.termora.app") - val chinaServer = Server(I18n.getString("termora.settings.account.server-china"), "https://account.termora.cn") - if (Application.isUnknownVersion()) { serverComboBox.addItem(Server("Localhost", "http://127.0.0.1:8080")) } - serverComboBox.addItem(singaporeServer) - serverComboBox.addItem(chinaServer) +// serverComboBox.addItem(singaporeServer) +// serverComboBox.addItem(chinaServer) val properties = DatabaseManager.getInstance().properties val servers = (runCatching { @@ -117,7 +118,7 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) { val dialog = this val newAction = object : AnAction(I18n.getString("termora.welcome.contextmenu.new")) { override fun actionPerformed(evt: AnActionEvent) { - if (serverComboBox.selectedItem == singaporeServer || serverComboBox.selectedItem == chinaServer) { + if (serverComboBox.itemCount < 1 || serverComboBox.selectedItem == singaporeServer || serverComboBox.selectedItem == chinaServer) { val c = NewServerDialog(dialog) c.isVisible = true val server = c.server ?: return @@ -142,18 +143,35 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) { } } } - val newServer = JXHyperlink(newAction) - newServer.isFocusable = false - serverComboBox.addItemListener { - if (it.stateChange == ItemEvent.SELECTED) { - if (serverComboBox.selectedItem == singaporeServer || serverComboBox.selectedItem == chinaServer) { - newAction.name = I18n.getString("termora.welcome.contextmenu.new") - } else { - newAction.name = I18n.getString("termora.remove") - } + + + fun refreshButton() { + if (serverComboBox.selectedItem == singaporeServer || serverComboBox.selectedItem == chinaServer || serverComboBox.itemCount < 1) { + newAction.name = I18n.getString("termora.welcome.contextmenu.new") + } else { + newAction.name = I18n.getString("termora.remove") } } + val newServer = JXHyperlink(newAction) + newServer.isFocusable = false + serverComboBox.addItemListener { refreshButton() } + + serverComboBox.model.addListDataListener(object : ListDataListener { + override fun intervalAdded(e: ListDataEvent?) { + refreshButton() + } + + override fun intervalRemoved(e: ListDataEvent?) { + refreshButton() + } + + override fun contentsChanged(e: ListDataEvent?) { + refreshButton() + } + + }) + return FormBuilder.create().layout(layout).debug(false).padding("0dlu, $FORM_MARGIN, 0dlu, $FORM_MARGIN") .add("${I18n.getString("termora.settings.account.server")}:").xy(1, rows) @@ -166,6 +184,7 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) { .build() } + override fun createOkAction(): AbstractAction { return okAction } @@ -250,7 +269,12 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) { override fun doOKAction() { if (isLoggingIn.get()) return - val server = serverComboBox.selectedItem as? Server ?: return + val server = serverComboBox.selectedItem as? Server + if (server == null) { + serverComboBox.outline = FlatClientProperties.OUTLINE_ERROR + serverComboBox.requestFocusInWindow() + return + } if (usernameTextField.text.isBlank()) { usernameTextField.outline = FlatClientProperties.OUTLINE_ERROR diff --git a/src/main/kotlin/app/termora/database/DataEntity.kt b/src/main/kotlin/app/termora/database/DataEntity.kt index 0eac655..5897874 100644 --- a/src/main/kotlin/app/termora/database/DataEntity.kt +++ b/src/main/kotlin/app/termora/database/DataEntity.kt @@ -1,13 +1,12 @@ package app.termora.database -import app.termora.LocalSecret import app.termora.randomUUID import org.apache.commons.lang3.StringUtils import org.jetbrains.exposed.v1.core.Column import org.jetbrains.exposed.v1.core.Table import org.jetbrains.exposed.v1.crypt.Algorithms -object DataEntity : Table() { +internal object DataEntity : Table() { val id: Column = char("id", length = 32).clientDefault { randomUUID() } /** @@ -45,8 +44,8 @@ object DataEntity : Table() { */ val data: Column = encryptedText( "data", Algorithms.AES_256_PBE_GCM( - LocalSecret.getInstance().password, - LocalSecret.getInstance().salt + DatabaseSecret.getInstance().password, + DatabaseSecret.getInstance().salt ) ) diff --git a/src/main/kotlin/app/termora/database/DatabaseManager.kt b/src/main/kotlin/app/termora/database/DatabaseManager.kt index 2a783db..8c24c53 100644 --- a/src/main/kotlin/app/termora/database/DatabaseManager.kt +++ b/src/main/kotlin/app/termora/database/DatabaseManager.kt @@ -27,6 +27,10 @@ import kotlin.reflect.KProperty class DatabaseManager private constructor() : Disposable { companion object { + + private const val DB_PASSWORD = "DB_PASSWORD" + private const val DB_SALT = "DB_SALT" + val log = LoggerFactory.getLogger(DatabaseManager::class.java)!! fun getInstance(): DatabaseManager { return ApplicationScope.forApplicationScope() @@ -42,6 +46,15 @@ class DatabaseManager private constructor() : Disposable { val appearance by lazy { Appearance(this) } val sftp by lazy { SFTP(this) } + + @Volatile + internal var dbPassword = StringUtils.EMPTY + private set + + @Volatile + internal var dbSalt = StringUtils.EMPTY + private set + private val map = Collections.synchronizedMap(mutableMapOf()) private val accountManager get() = AccountManager.getInstance() @@ -59,6 +72,14 @@ class DatabaseManager private constructor() : Disposable { driver = "org.sqlite.JDBC", user = "sa" ) + // 没有加密的表优先创建 + if (isExists.not()) { + transaction(database) { SchemaUtils.create(UnsafeSettingEntity) } + } + + // 获取密钥信息 + transaction(database) { DatabaseSecret.getInstance(database) } + // 设置数据库版本号,便于后续升级 if (isExists.not()) { transaction(database) { diff --git a/src/main/kotlin/app/termora/database/DatabaseSecret.kt b/src/main/kotlin/app/termora/database/DatabaseSecret.kt new file mode 100644 index 0000000..5158446 --- /dev/null +++ b/src/main/kotlin/app/termora/database/DatabaseSecret.kt @@ -0,0 +1,65 @@ +package app.termora.database + +import app.termora.ApplicationScope +import org.apache.commons.codec.digest.DigestUtils +import org.apache.commons.lang3.RandomUtils +import org.apache.commons.lang3.StringUtils +import org.jetbrains.exposed.v1.jdbc.Database +import org.jetbrains.exposed.v1.jdbc.insert +import org.jetbrains.exposed.v1.jdbc.selectAll +import org.jetbrains.exposed.v1.jdbc.transactions.transaction + +internal class DatabaseSecret(database: Database) { + + companion object { + private const val PASSWORD = "__DB_PASSWORD" + private const val SALT = "__DB_SALT" + + fun getInstance(database: Database): DatabaseSecret { + return ApplicationScope.forApplicationScope() + .getOrCreate(DatabaseSecret::class) { DatabaseSecret(database) } + } + + fun getInstance(): DatabaseSecret { + return ApplicationScope.forApplicationScope().get(DatabaseSecret::class) + } + } + + @Volatile + var password: String = StringUtils.EMPTY + private set + + @Volatile + var salt: String = StringUtils.EMPTY + private set + + init { + transaction(database) { + val unsafeSettings = UnsafeSettingEntity.selectAll() + .map { it[UnsafeSettingEntity.name] to it[UnsafeSettingEntity.value] } + .associateBy { it.first } + if (unsafeSettings.containsKey(PASSWORD)) { + password = unsafeSettings.getValue(PASSWORD).second + } else { + password = + StringUtils.substring(DigestUtils.sha256Hex(RandomUtils.secureStrong().randomBytes(128)), 0, 16) + UnsafeSettingEntity.insert { + it[UnsafeSettingEntity.name] = PASSWORD + it[UnsafeSettingEntity.value] = password + } + } + + if (unsafeSettings.containsKey(SALT)) { + salt = unsafeSettings.getValue(SALT).second + } else { + salt = + StringUtils.substring(DigestUtils.sha256Hex(RandomUtils.secureStrong().randomBytes(128)), 0, 12) + UnsafeSettingEntity.insert { + it[UnsafeSettingEntity.name] = SALT + it[UnsafeSettingEntity.value] = salt + } + } + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/database/SettingEntity.kt b/src/main/kotlin/app/termora/database/SettingEntity.kt index 0c7caa7..c242952 100644 --- a/src/main/kotlin/app/termora/database/SettingEntity.kt +++ b/src/main/kotlin/app/termora/database/SettingEntity.kt @@ -1,19 +1,18 @@ package app.termora.database -import app.termora.LocalSecret import app.termora.randomUUID import org.apache.commons.lang3.StringUtils import org.jetbrains.exposed.v1.core.Column import org.jetbrains.exposed.v1.core.Table import org.jetbrains.exposed.v1.crypt.Algorithms -object SettingEntity : Table() { +internal object SettingEntity : Table() { val id: Column = char("id", length = 32).clientDefault { randomUUID() } val name: Column = varchar("name", length = 128).index() val value: Column = encryptedText( "value", Algorithms.AES_256_PBE_GCM( - LocalSecret.getInstance().password, - LocalSecret.getInstance().salt + DatabaseSecret.getInstance().password, + DatabaseSecret.getInstance().salt ) ) diff --git a/src/main/kotlin/app/termora/database/UnsafeSettingEntity.kt b/src/main/kotlin/app/termora/database/UnsafeSettingEntity.kt new file mode 100644 index 0000000..7455edb --- /dev/null +++ b/src/main/kotlin/app/termora/database/UnsafeSettingEntity.kt @@ -0,0 +1,23 @@ +package app.termora.database + +import app.termora.randomUUID +import org.apache.commons.lang3.StringUtils +import org.jetbrains.exposed.v1.core.Column +import org.jetbrains.exposed.v1.core.Table + +internal object UnsafeSettingEntity : Table() { + val id: Column = char("id", length = 32).clientDefault { randomUUID() } + val name: Column = varchar("name", length = 128).index() + val value: Column = text("value") + + /** + * 备用字段1-3 + */ + val extra1: Column = text("extra1").clientDefault { StringUtils.EMPTY } + val extra2: Column = text("extra2").clientDefault { StringUtils.EMPTY } + val extra3: Column = text("extra3").clientDefault { StringUtils.EMPTY } + + override val primaryKey: PrimaryKey get() = PrimaryKey(id) + override val tableName: String + get() = "tb_unsafe_setting" +}