From e4908dfd1e53d4f301e885a0eae62cd81a039961 Mon Sep 17 00:00:00 2001 From: LamGC Date: Thu, 4 Jan 2024 17:01:04 +0000 Subject: [PATCH] Someting --- build.gradle.kts | 11 +++ src/main/kotlin/Databases.kt | 63 +++++++++++++++++ src/main/kotlin/ExtensionConfig.kt | 1 - src/main/kotlin/MicrosoftAccountManager.kt | 72 ++++++++++++++++++++ src/main/kotlin/OneDriveTransferExtension.kt | 56 ++++++++++----- src/main/kotlin/Services.kt | 7 ++ 6 files changed, 193 insertions(+), 17 deletions(-) create mode 100644 src/main/kotlin/Databases.kt create mode 100644 src/main/kotlin/MicrosoftAccountManager.kt create mode 100644 src/main/kotlin/Services.kt diff --git a/build.gradle.kts b/build.gradle.kts index e1f5538..21e9fd6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,6 +20,17 @@ dependencies { implementation("org.slf4j:slf4j-api:2.0.10") implementation("net.lamgc:scalabot-extension:0.6.1") + val exposedVersion = "0.45.0" + implementation("org.jetbrains.exposed:exposed-core:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-crypt:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion") + implementation("org.jetbrains.exposed:exposed-kotlin-datetime:$exposedVersion") + + implementation("org.xerial:sqlite-jdbc:3.44.1.0") + implementation("mysql:mysql-connector-java:8.0.33") + implementation("com.zaxxer:HikariCP:5.1.0") + implementation("com.microsoft.graph:microsoft-graph:5.77.0") implementation("com.azure:azure-identity:1.11.1") diff --git a/src/main/kotlin/Databases.kt b/src/main/kotlin/Databases.kt new file mode 100644 index 0000000..bcd5832 --- /dev/null +++ b/src/main/kotlin/Databases.kt @@ -0,0 +1,63 @@ +package net.lamgc.scext.onedrive_transfer + +import com.microsoft.aad.msal4j.ITokenCacheAccessAspect +import com.microsoft.aad.msal4j.ITokenCacheAccessContext +import org.jetbrains.exposed.dao.LongEntity +import org.jetbrains.exposed.dao.LongEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.LongIdTable +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.transactions.transaction + +object MicrosoftAccounts : LongIdTable() { + val telegramUserId = long("tg_user_id").uniqueIndex() + val accountId = varchar("account_id", 128) + val userName = varchar("user_name", 96) + +} + +class MicrosoftAccount(id: EntityID) : LongEntity(id) { + var telegramUserId by MicrosoftAccounts.telegramUserId + var accountId by MicrosoftAccounts.accountId + var userName by MicrosoftAccounts.accountId + + companion object : LongEntityClass(MicrosoftAccounts) +} + +object TokenCaches : LongIdTable() { + val accountId = varchar("access_id", 256).uniqueIndex() + val cache = text("cache_data") +} + +class TokenCache(id: EntityID) : LongEntity(id) { + var accountId by TokenCaches.accountId + var cache by TokenCaches.cache + + companion object : LongEntityClass(TokenCaches) +} + +class DatabaseTokenCache(private val db: Database) : ITokenCacheAccessAspect { + + override fun beforeCacheAccess(context: ITokenCacheAccessContext) { + transaction(db) { + TokenCache.find { TokenCaches.accountId eq context.account().homeAccountId() }.firstOrNull()?.let { + context.tokenCache().deserialize(it.cache) + } + } + } + + override fun afterCacheAccess(context: ITokenCacheAccessContext) { + transaction(db) { + val existCache = + TokenCache.find { TokenCaches.accountId eq context.account().homeAccountId() }.firstOrNull() + if (existCache == null) { + TokenCache.new { + accountId = context.account().homeAccountId() + cache = context.tokenCache().serialize() + } + } else { + existCache.cache = context.tokenCache().serialize() + } + } + } +} diff --git a/src/main/kotlin/ExtensionConfig.kt b/src/main/kotlin/ExtensionConfig.kt index ff6d8cb..6bf9c28 100644 --- a/src/main/kotlin/ExtensionConfig.kt +++ b/src/main/kotlin/ExtensionConfig.kt @@ -3,5 +3,4 @@ package net.lamgc.scext.onedrive_transfer data class ExtensionConfig( val clientId: String, val clientSecret: String, - val authUrl: String? ) diff --git a/src/main/kotlin/MicrosoftAccountManager.kt b/src/main/kotlin/MicrosoftAccountManager.kt new file mode 100644 index 0000000..89d81b3 --- /dev/null +++ b/src/main/kotlin/MicrosoftAccountManager.kt @@ -0,0 +1,72 @@ +package net.lamgc.scext.onedrive_transfer + +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.transactions.TransactionManager +import org.jetbrains.exposed.sql.transactions.transaction +import com.microsoft.aad.msal4j.* +import java.sql.Connection +import java.net.URL +import java.net.URI + +class MicrosoftAccountManager(private val authClient: ConfidentialClientApplication, private val db: Database) { + + + init { + TransactionManager.manager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE + } + + fun getMicrosoftByTelegramUser(userId: Long): MicrosoftAccount? { + return transaction(db) { + return@transaction MicrosoftAccount.find { MicrosoftAccounts.telegramUserId eq userId }.firstOrNull() + } + } + + fun createAuthorizationRequest(userId: Long): URL { + val parameters = AuthorizationRequestUrlParameters + .builder("http://localhost:45678/", OAUTH2_SCOPE) + .responseMode(ResponseMode.QUERY) + .prompt(Prompt.SELECT_ACCOUNT) + .build() + return authClient.getAuthorizationRequestUrl(parameters) + } + + fun updateAccount(userId: Long, token: String): MicrosoftAccount { + val future = authClient.acquireToken( + AuthorizationCodeParameters + .builder(token, URI.create("http://localhost:45678/")) + .build() + ) + val result = future.get() + return transaction(db) { + val account = MicrosoftAccount.find { MicrosoftAccounts.telegramUserId eq userId }.firstOrNull() + account?.apply { + accountId = result.account().homeAccountId() + userName = result.account().username() + } + ?: MicrosoftAccount.new { + telegramUserId = userId + accountId = result.account().homeAccountId() + userName = result.account().username() + } + } + } + + companion object { + const val AUTHORITY = "https://login.microsoftonline.com/common" + val OAUTH2_SCOPE = setOf( + "User.Read", + "Files.Read", + "Files.ReadWrite", + "Files.Read.All", + "Files.ReadWrite.All", + "Sites.ReadWrite.All", + "offline_access" + ) + + fun getTokenFromUrl(url: URL): String { + return url.query.split("&").find { it.startsWith("code=") }?.substring(5) ?: throw IllegalArgumentException( + "Invalid URL." + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/OneDriveTransferExtension.kt b/src/main/kotlin/OneDriveTransferExtension.kt index ab8b0df..d2ca8ff 100644 --- a/src/main/kotlin/OneDriveTransferExtension.kt +++ b/src/main/kotlin/OneDriveTransferExtension.kt @@ -1,17 +1,23 @@ package net.lamgc.scext.onedrive_transfer -import com.azure.identity.AuthorizationCodeCredentialBuilder import com.google.gson.Gson +import com.microsoft.aad.msal4j.* +import com.microsoft.graph.requests.GraphServiceClient +import org.jetbrains.exposed.sql.Database import org.telegram.abilitybots.api.bot.BaseAbilityBot import org.telegram.abilitybots.api.objects.Ability import org.telegram.abilitybots.api.objects.Locality import org.telegram.abilitybots.api.objects.Privacy +import org.telegram.abilitybots.api.objects.Reply import org.telegram.abilitybots.api.util.AbilityExtension import java.io.File +import java.net.URL class OneDriveTransferExtension(val bot: BaseAbilityBot, val dataFolder: File) : AbilityExtension { private val config: ExtensionConfig + private val authClient: ConfidentialClientApplication + private val accountManager: MicrosoftAccountManager init { val configFile = File(dataFolder, "config.json") @@ -20,25 +26,43 @@ class OneDriveTransferExtension(val bot: BaseAbilityBot, val dataFolder: File) : configFile.writeText("{}") } config = Gson().fromJson(configFile.reader(), ExtensionConfig::class.java) - - val credentialBuilder = AuthorizationCodeCredentialBuilder() - .clientId(config.clientId) - .clientSecret(config.clientSecret) - .redirectUrl("http://localhost:8080") + val db = Database.connect("jdbc:sqlite:${File(dataFolder, "./data.db").canonicalPath}", "org.sqlite.JDBC") + authClient = ConfidentialClientApplication.builder( + config.clientId, + ClientCredentialFactory.createFromSecret(config.clientSecret), + ) + .authority(MicrosoftAccountManager.AUTHORITY) + .setTokenCacheAccessAspect(DatabaseTokenCache(db)) .build() + accountManager = MicrosoftAccountManager(authClient, db) } - fun loginOneDrive(): Ability { - return Ability - .builder() - .name("odt_login") - .info("登录 OneDrive 账户.") - .locality(Locality.USER) - .privacy(Privacy.PUBLIC) - .action { ctx -> + fun loginOneDrive(): Ability = Ability + .builder() + .name("odt_login") + .info("登录 OneDrive 账户.") + .locality(Locality.USER) + .privacy(Privacy.PUBLIC) + .action { ctx -> + val url = accountManager.createAuthorizationRequest(ctx.user().id) + ctx.bot().silent().send(""" + 请使用以下链接进行登录: + $url + + ------------------------------------------- + 登录成功后会显示无法访问,是正常情况,请将地址栏的链接发回给机器人。 + """.trimIndent(), ctx.chatId()) + } + .reply(Reply.of( + {bot, upd -> + val token = MicrosoftAccountManager.getTokenFromUrl(URL(upd.message.text.trim())) + val account = accountManager.updateAccount(upd.message.chat.id, token) + }, + { upd -> + upd.hasMessage() } - .build() - } + )) + .build() } diff --git a/src/main/kotlin/Services.kt b/src/main/kotlin/Services.kt new file mode 100644 index 0000000..daf6bb0 --- /dev/null +++ b/src/main/kotlin/Services.kt @@ -0,0 +1,7 @@ +package net.lamgc.scext.onedrive_transfer + +class OneDriveTransferService() { + + + +} \ No newline at end of file