2 Commits

Author SHA1 Message Date
a528030cbb Shamrock: 实现鉴权操作
Signed-off-by: 白池 <whitechi73@outlook.com>
2024-03-10 16:05:20 +08:00
bbdfb7c04e Shamrock: 实现鉴权
Signed-off-by: 白池 <whitechi73@outlook.com>
2024-03-10 16:04:52 +08:00
8 changed files with 206 additions and 37 deletions

2
kritor

Submodule kritor updated: f4fa15754e...505d80b2ee

View File

@ -0,0 +1,64 @@
package kritor.auth
import io.grpc.ForwardingServerCallListener
import io.grpc.Metadata
import io.grpc.ServerCall
import io.grpc.ServerCallHandler
import io.grpc.ServerInterceptor
import moe.fuqiuluo.shamrock.config.ActiveTicket
import moe.fuqiuluo.shamrock.config.ShamrockConfig
object AuthInterceptor: ServerInterceptor {
/**
* Intercept [ServerCall] dispatch by the `next` [ServerCallHandler]. General
* semantics of [ServerCallHandler.startCall] apply and the returned
* [io.grpc.ServerCall.Listener] must not be `null`.
*
*
* If the implementation throws an exception, `call` will be closed with an error.
* Implementations must not throw an exception if they started processing that may use `call` on another thread.
*
* @param call object to receive response messages
* @param headers which can contain extra call metadata from [ClientCall.start],
* e.g. authentication credentials.
* @param next next processor in the interceptor chain
* @return listener for processing incoming messages for `call`, never `null`.
*/
override fun <ReqT : Any?, RespT : Any?> interceptCall(
call: ServerCall<ReqT, RespT>,
headers: Metadata?,
next: ServerCallHandler<ReqT, RespT>
): ServerCall.Listener<ReqT> {
val methodName = call.methodDescriptor.fullMethodName
val ticket = getAllTicket()
if (ticket.isNotEmpty() && !methodName.startsWith("Auth")) {
val ticketHeader = headers?.get(Metadata.Key.of("ticket", Metadata.ASCII_STRING_MARSHALLER))
if (ticketHeader == null || ticketHeader !in ticket) {
call.close(io.grpc.Status.UNAUTHENTICATED.withDescription("Invalid ticket"), Metadata())
return object: ServerCall.Listener<ReqT>() {}
}
}
return object: ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(next.startCall(call, headers)) {
}
}
private fun getAllTicket(): List<String> {
val result = arrayListOf<String>()
val activeTicketName = ActiveTicket.name()
var index = 0
while (true) {
val ticket = ShamrockConfig.getProperty(activeTicketName + if (index == 0) "" else ".$index", null)
if (ticket.isNullOrEmpty()) {
if (index == 0) {
return result
} else {
break
}
} else {
result.add(ticket)
}
index++
}
return result
}
}

View File

@ -3,11 +3,11 @@ package kritor.server
import io.grpc.Grpc import io.grpc.Grpc
import io.grpc.InsecureServerCredentials import io.grpc.InsecureServerCredentials
import io.grpc.ServerBuilder
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.asExecutor
import kritor.auth.AuthInterceptor
import kritor.service.* import kritor.service.*
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -16,7 +16,10 @@ class KritorServer(
private val port: Int private val port: Int
): CoroutineScope { ): CoroutineScope {
private val server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) private val server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
.executor(Dispatchers.IO.asExecutor())
.intercept(AuthInterceptor)
.addService(Authentication) .addService(Authentication)
.addService(ContactService)
.build()!! .build()!!
fun start(block: Boolean = false) { fun start(block: Boolean = false) {

View File

@ -0,0 +1,47 @@
package kritor.service
import io.grpc.Status
import io.grpc.StatusRuntimeException
import io.kritor.AuthCode
import io.kritor.authRsp
import io.kritor.contact.ContactServiceGrpcKt
import io.kritor.contact.ProfileCard
import io.kritor.contact.ProfileCardRequest
import io.kritor.contact.profileCard
import moe.fuqiuluo.shamrock.config.ActiveTicket
import moe.fuqiuluo.shamrock.config.ShamrockConfig
import qq.service.contact.ContactHelper
object ContactService: ContactServiceGrpcKt.ContactServiceCoroutineImplBase() {
override suspend fun getProfileCard(request: ProfileCardRequest): ProfileCard {
val uin = if (request.hasUin()) request.uin
else ContactHelper.getUinByUidAsync(request.uid).toLong()
val contact = ContactHelper.getProfileCard(uin)
contact.onFailure {
throw StatusRuntimeException(Status.INTERNAL
.withDescription(it.stackTraceToString())
)
}
contact.onSuccess {
return profileCard {
this.uin = it.uin.toLong()
this.uid = if (request.hasUid()) request.uid
else ContactHelper.getUidByUinAsync(it.uin.toLong())
this.name = it.strNick ?: ""
this.remark = it.strReMark ?: ""
this.level = it.iQQLevel
this.birthday = it.lBirthday
this.loginDay = it.lLoginDays.toInt()
this.voteCnt = it.lVoteCount.toInt()
this.qid = it.qid ?: ""
this.isSchoolVerified = it.schoolVerifiedFlag
}
}
throw StatusRuntimeException(Status.INTERNAL
.withDescription("logic failed")
)
}
}

View File

@ -1,32 +0,0 @@
package moe.fuqiuluo.shamrock.helper
import kotlinx.coroutines.suspendCancellableCoroutine
import moe.fuqiuluo.shamrock.internals.NTServiceFetcher
import kotlin.coroutines.resume
internal object ContactHelper {
suspend fun getUinByUidAsync(uid: String): String {
if (uid.isBlank() || uid == "0") {
return "0"
}
val kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession
return suspendCancellableCoroutine { continuation ->
sessionService.uixConvertService.getUin(hashSetOf(uid)) {
continuation.resume(it)
}
}[uid]?.toString() ?: "0"
}
suspend fun getUidByUinAsync(peerId: Long): String {
val kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession
return suspendCancellableCoroutine { continuation ->
sessionService.uixConvertService.getUid(hashSetOf(peerId)) {
continuation.resume(it)
}
}[peerId]!!
}
}

View File

@ -8,7 +8,7 @@ import com.arthenica.ffmpegkit.FFmpegKit
import com.arthenica.ffmpegkit.FFprobeKit import com.arthenica.ffmpegkit.FFprobeKit
import com.arthenica.ffmpegkit.ReturnCode import com.arthenica.ffmpegkit.ReturnCode
import com.tencent.qqnt.kernel.nativeinterface.QQNTWrapperUtil import com.tencent.qqnt.kernel.nativeinterface.QQNTWrapperUtil
import qq.service.contact.LocalCacheHelper import qq.service.internals.LocalCacheHelper
import moe.fuqiuluo.shamrock.helper.Level import moe.fuqiuluo.shamrock.helper.Level
import moe.fuqiuluo.shamrock.helper.LogCenter import moe.fuqiuluo.shamrock.helper.LogCenter
import java.io.File import java.io.File

View File

@ -0,0 +1,87 @@
package qq.service.contact
import com.tencent.common.app.AppInterface
import com.tencent.mobileqq.data.Card
import com.tencent.mobileqq.profilecard.api.IProfileDataService
import com.tencent.mobileqq.profilecard.api.IProfileProtocolService
import com.tencent.mobileqq.profilecard.observer.ProfileCardObserver
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import moe.fuqiuluo.shamrock.internals.NTServiceFetcher
import qq.service.QQInterfaces
import kotlin.coroutines.resume
object ContactHelper: QQInterfaces() {
private val refreshCardLock by lazy { Mutex() }
suspend fun getProfileCard(uin: Long): Result<Card> {
return getProfileCardFromCache(uin).onFailure {
return refreshAndGetProfileCard(uin)
}
}
fun getProfileCardFromCache(uin: Long): Result<Card> {
val profileDataService = app
.getRuntimeService(IProfileDataService::class.java, "all")
val card = profileDataService.getProfileCard(uin.toString(), true)
return if (card == null || card.strNick.isNullOrEmpty()) {
Result.failure(Exception("unable to fetch profile card"))
} else {
Result.success(card)
}
}
suspend fun refreshAndGetProfileCard(uin: Long): Result<Card> {
require(app is AppInterface)
val dataService = app
.getRuntimeService(IProfileDataService::class.java, "all")
val card = refreshCardLock.withLock {
suspendCancellableCoroutine {
app.addObserver(object: ProfileCardObserver() {
override fun onGetProfileCard(success: Boolean, obj: Any) {
app.removeObserver(this)
if (!success || obj !is Card) {
it.resume(null)
} else {
dataService.saveProfileCard(obj)
it.resume(obj)
}
}
})
app.getRuntimeService(IProfileProtocolService::class.java, "all")
.requestProfileCard(app.currentUin, uin.toString(), 12, 0L, 0.toByte(), 0L, 0L, null, "", 0L, 10004, null, 0.toByte())
}
}
return if (card == null || card.strNick.isNullOrEmpty()) {
Result.failure(Exception("unable to fetch profile card"))
} else {
Result.success(card)
}
}
suspend fun getUinByUidAsync(uid: String): String {
if (uid.isBlank() || uid == "0") {
return "0"
}
val kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession
return suspendCancellableCoroutine { continuation ->
sessionService.uixConvertService.getUin(hashSetOf(uid)) {
continuation.resume(it)
}
}[uid]?.toString() ?: "0"
}
suspend fun getUidByUinAsync(peerId: Long): String {
val kernelService = NTServiceFetcher.kernelService
val sessionService = kernelService.wrapperSession
return suspendCancellableCoroutine { continuation ->
sessionService.uixConvertService.getUid(hashSetOf(peerId)) {
continuation.resume(it)
}
}[peerId]!!
}
}

View File

@ -1,4 +1,4 @@
package qq.service.contact package qq.service.internals
import moe.fuqiuluo.shamrock.utils.FileUtils import moe.fuqiuluo.shamrock.utils.FileUtils
import mqq.app.MobileQQ import mqq.app.MobileQQ