Shamrock: 实现鉴权

Signed-off-by: 白池 <whitechi73@outlook.com>
This commit is contained in:
白池 2024-03-10 16:04:52 +08:00
parent 1afc0ac6a6
commit bbdfb7c04e
8 changed files with 204 additions and 37 deletions

2
kritor

@ -1 +1 @@
Subproject commit f4fa15754e266182b5f2c08c54c88c21c61eb065
Subproject commit 505d80b2eec8d5f226ed28390fb15f4ca41b4c70

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,10 @@ package kritor.server
import io.grpc.Grpc
import io.grpc.InsecureServerCredentials
import io.grpc.ServerBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.asExecutor
import kritor.service.*
import moe.fuqiuluo.shamrock.helper.LogCenter
import kotlin.coroutines.CoroutineContext
@ -16,7 +15,9 @@ class KritorServer(
private val port: Int
): CoroutineScope {
private val server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
.executor(Dispatchers.IO.asExecutor())
.addService(Authentication)
.addService(ContactService)
.build()!!
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.ReturnCode
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.LogCenter
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 mqq.app.MobileQQ