Skip to content

Commit

Permalink
Migrate from abandoned java-gitlab-api to gitlab4j-api
Browse files Browse the repository at this point in the history
  • Loading branch information
slonopotamus committed May 12, 2024
1 parent fe6156d commit 71481f9
Show file tree
Hide file tree
Showing 21 changed files with 181 additions and 149 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Expand Up @@ -86,7 +86,7 @@ dependencies {
implementation("org.mapdb:mapdb:3.1.0")
implementation("com.unboundid:unboundid-ldapsdk:7.0.0")
implementation("org.eclipse.jetty:jetty-servlet:11.0.20")
implementation("org.gitlab:java-gitlab-api:4.1.1")
implementation("org.gitlab4j:gitlab4j-api:6.0.0-rc.4")
implementation("org.bitbucket.b_c:jose4j:0.9.6")
implementation("com.github.zeripath:java-gitea-api:1.18.0")

Expand Down
45 changes: 25 additions & 20 deletions src/main/kotlin/svnserver/ext/gitlab/auth/GitLabUserDB.kt
Expand Up @@ -7,9 +7,8 @@
*/
package svnserver.ext.gitlab.auth

import org.gitlab.api.GitlabAPI
import org.gitlab.api.GitlabAPIException
import org.gitlab.api.models.GitlabUser
import org.gitlab4j.api.GitLabApi
import org.gitlab4j.api.GitLabApiException
import svnserver.Loggers
import svnserver.UserType
import svnserver.auth.Authenticator
Expand All @@ -23,6 +22,7 @@ import svnserver.ext.gitlab.config.GitLabToken
import java.io.FileNotFoundException
import java.io.IOException
import java.net.HttpURLConnection
import org.gitlab4j.api.models.User as GitLabUser

/**
* GitLab user authentiation.
Expand All @@ -37,18 +37,19 @@ class GitLabUserDB(private val config: GitLabUserDBConfig, context: SharedContex
override fun check(username: String, password: String): User? {
return try {
val token: GitLabToken = config.authentication.obtainAccessToken(context.gitLabUrl, username, password)
val api: GitlabAPI = GitLabContext.connect(context.gitLabUrl, token)
val session = api.currentSession
if (session.username == username) {
createUser(session, password)
} else {
// This can happen when user authenticates using access token (so username is not used) but enters wrong username.
// While we properly calculate username, svn *client* thinks that their username is what user has entered.
log.warn("User password check error: expected username=${session.username} but got username=$username")
null
GitLabContext.connect(context.gitLabUrl, token).use {
val session = it.userApi.currentUser
if (session.username == username) {
createUser(session, password)
} else {
// This can happen when user authenticates using access token (so username is not used) but enters wrong username.
// While we properly calculate username, svn *client* thinks that their username is what user has entered.
log.warn("User password check error: expected username=${session.username} but got username=$username")
null
}
}
} catch (e: GitlabAPIException) {
if (e.responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
} catch (e: GitLabApiException) {
if (e.httpStatus == HttpURLConnection.HTTP_UNAUTHORIZED) {
return null
}
log.warn("User password check error: $username", e)
Expand All @@ -59,13 +60,17 @@ class GitLabUserDB(private val config: GitLabUserDBConfig, context: SharedContex
}
}

private fun createUser(user: GitlabUser, password: String?): User {
private fun createUser(user: GitLabUser, password: String?): User {
return User.create(user.username, user.name, user.email, user.id.toString(), UserType.GitLab, if (password == null) null else LfsCredentials(user.username, password))
}

override fun lookupByUserName(username: String): User? {
return try {
createUser(context.connect().getUserViaSudo(username), null)
val user = GitLabApi(context.gitLabUrl, context.config.getToken().type, context.config.getToken().value).use {
it.sudo(username)
it.userApi.currentUser
}
createUser(user, null)
} catch (e: FileNotFoundException) {
null
} catch (e: IOException) {
Expand All @@ -78,7 +83,7 @@ class GitLabUserDB(private val config: GitLabUserDBConfig, context: SharedContex
val userId = removePrefix(external, PREFIX_USER)
if (userId != null) {
return try {
createUser(context.connect().getUser(userId), null)
createUser(context.api.userApi.getUser(userId), null)
} catch (e: FileNotFoundException) {
null
} catch (e: IOException) {
Expand All @@ -89,7 +94,7 @@ class GitLabUserDB(private val config: GitLabUserDBConfig, context: SharedContex
val keyId = removePrefix(external, PREFIX_KEY)
return if (keyId != null) {
try {
createUser(context.connect().getSSHKey(keyId).user, null)
createUser(context.api.keysAPI.getUserBySSHKeyFingerprint(keyId.toString()).user, null)
} catch (e: FileNotFoundException) {
null
} catch (e: IOException) {
Expand All @@ -99,9 +104,9 @@ class GitLabUserDB(private val config: GitLabUserDBConfig, context: SharedContex
} else null
}

private fun removePrefix(glId: String, prefix: String): Int? {
private fun removePrefix(glId: String, prefix: String): Long? {
if (glId.startsWith(prefix)) {
var result = 0
var result = 0L
for (i in prefix.length until glId.length) {
val c = glId[i]
if (c < '0' || c > '9') return null
Expand Down
Expand Up @@ -7,7 +7,7 @@
*/
package svnserver.ext.gitlab.auth

import org.gitlab.api.TokenType
import org.gitlab4j.api.Constants
import svnserver.auth.UserDB
import svnserver.config.UserDBConfig
import svnserver.context.SharedContext
Expand Down Expand Up @@ -35,12 +35,12 @@ enum class GitlabAuthentication {
},
AccessToken {
override fun obtainAccessToken(gitLabUrl: String, username: String, password: String): GitLabToken {
return GitLabToken(TokenType.ACCESS_TOKEN, password)
return GitLabToken(Constants.TokenType.ACCESS, password)
}
},
PrivateToken {
override fun obtainAccessToken(gitLabUrl: String, username: String, password: String): GitLabToken {
return GitLabToken(TokenType.PRIVATE_TOKEN, password)
return GitLabToken(Constants.TokenType.PRIVATE, password)
}
};

Expand Down
6 changes: 3 additions & 3 deletions src/main/kotlin/svnserver/ext/gitlab/config/GitLabConfig.kt
Expand Up @@ -7,7 +7,7 @@
*/
package svnserver.ext.gitlab.config

import org.gitlab.api.TokenType
import org.gitlab4j.api.Constants
import ru.bozaro.gitlfs.client.auth.AuthProvider
import ru.bozaro.gitlfs.client.auth.BasicAuthProvider
import svnserver.auth.User
Expand All @@ -26,15 +26,15 @@ import java.net.URI
*
* @author Artem V. Navrotskiy <bozaro@users.noreply.github.com>
*/
class GitLabConfig private constructor(var url: String, private var tokenType: TokenType, private var token: String) : SharedConfig {
class GitLabConfig private constructor(var url: String, private var tokenType: Constants.TokenType, private var token: String) : SharedConfig {
var hookPath = "_hooks/gitlab"
private var lfsMode: LfsMode? = HttpLfsMode.instance
var gitalyBinDir = "/opt/gitlab/embedded/bin"
var gitalySocket = "/var/opt/gitlab/gitaly/gitaly.socket"
var gitalyToken = "secret token"
var glProtocol: GLProtocol = GLProtocol.Web

constructor() : this("http://localhost/", TokenType.PRIVATE_TOKEN, "")
constructor() : this("http://localhost/", Constants.TokenType.PRIVATE, "")
constructor(url: String, token: GitLabToken) : this(url, token.type, token.value)

override fun create(context: SharedContext) {
Expand Down
39 changes: 22 additions & 17 deletions src/main/kotlin/svnserver/ext/gitlab/config/GitLabContext.kt
Expand Up @@ -13,10 +13,9 @@ import com.google.api.client.auth.oauth2.TokenResponseException
import com.google.api.client.http.HttpTransport
import com.google.api.client.http.javanet.NetHttpTransport
import com.google.api.client.json.jackson2.JacksonFactory
import org.gitlab.api.GitlabAPI
import org.gitlab.api.GitlabAPIException
import org.gitlab.api.TokenType
import org.gitlab.api.models.GitlabSession
import org.gitlab4j.api.Constants
import org.gitlab4j.api.GitLabApi
import org.gitlab4j.api.models.User
import svnserver.context.Shared
import svnserver.context.SharedContext
import java.io.IOException
Expand All @@ -28,24 +27,29 @@ import java.net.HttpURLConnection
* @author Artem V. Navrotskiy <bozaro@users.noreply.github.com>
*/
class GitLabContext internal constructor(val config: GitLabConfig) : Shared {

@Throws(IOException::class)
fun connect(username: String, password: String): GitlabSession {
fun connect(username: String, password: String): User {
val token = obtainAccessToken(gitLabUrl, username, password, false)
val api = connect(gitLabUrl, token)
return api.currentSession
}

fun connect(): GitlabAPI {
return Companion.connect(gitLabUrl, token)
connect(gitLabUrl, token).use {
return it.userApi.currentUser
}
}

val api = Companion.connect(gitLabUrl, token)
val gitLabUrl: String
get() = config.url
val token: GitLabToken
get() = config.getToken()
val hookPath: String
get() = config.hookPath

override fun close() {
super.close()

api.close()
}

companion object {
private val transport: HttpTransport = NetHttpTransport()
fun sure(context: SharedContext): GitLabContext {
Expand All @@ -57,20 +61,21 @@ class GitLabContext internal constructor(val config: GitLabConfig) : Shared {
return try {
val tokenServerUrl = OAuthGetAccessToken(gitlabUrl + "/oauth/token?scope=api" + if (sudoScope) "%20sudo" else "")
val oauthResponse = PasswordTokenRequest(transport, JacksonFactory.getDefaultInstance(), tokenServerUrl, username, password).execute()
GitLabToken(TokenType.ACCESS_TOKEN, oauthResponse.accessToken)
GitLabToken(Constants.TokenType.ACCESS, oauthResponse.accessToken)
} catch (e: TokenResponseException) {
if (sudoScope && e.statusCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
// Fallback for pre-10.2 gitlab versions
val session = GitlabAPI.connect(gitlabUrl, username, password)
GitLabToken(TokenType.PRIVATE_TOKEN, session.privateToken)
GitLabApi.oauth2Login(gitlabUrl, username, password).use {
GitLabToken(it.tokenType, it.authToken)
}
} else {
throw GitlabAPIException(e.message, e.statusCode, e)
throw e
}
}
}

fun connect(gitlabUrl: String, token: GitLabToken): GitlabAPI {
return GitlabAPI.connect(gitlabUrl, token.value, token.type)
fun connect(gitlabUrl: String, token: GitLabToken): GitLabApi {
return GitLabApi(gitlabUrl, token.type, token.value)
}
}
}
4 changes: 2 additions & 2 deletions src/main/kotlin/svnserver/ext/gitlab/config/GitLabToken.kt
Expand Up @@ -7,6 +7,6 @@
*/
package svnserver.ext.gitlab.config

import org.gitlab.api.TokenType
import org.gitlab4j.api.Constants

class GitLabToken(val type: TokenType, val value: String)
class GitLabToken(val type: Constants.TokenType, val value: String)
44 changes: 26 additions & 18 deletions src/main/kotlin/svnserver/ext/gitlab/mapping/GitLabAccess.kt
Expand Up @@ -7,10 +7,10 @@
*/
package svnserver.ext.gitlab.mapping

import org.gitlab.api.GitlabAPI
import org.gitlab.api.models.GitlabAccessLevel
import org.gitlab.api.models.GitlabProject
import org.gitlab.api.models.GitlabUser
import org.gitlab4j.api.GitLabApi
import org.gitlab4j.api.models.AccessLevel
import org.gitlab4j.api.models.Owner
import org.gitlab4j.api.models.Project
import org.mapdb.HTreeMap
import org.mapdb.Serializer
import ru.bozaro.gitlfs.common.JsonHelper
Expand All @@ -27,14 +27,14 @@ import java.nio.file.Path
import java.util.*
import java.util.concurrent.TimeUnit

private class GitlabUserCache(user: GitlabUser) : Serializable {
val id: Int? = user.id
private class GitlabUserCache(user: Owner) : Serializable {
val id: Long? = user.id
val name: String? = user.name
}

private class GitlabProjectCache(project: GitlabProject) : Serializable {
val projectAccess: GitlabAccessLevel? = project.permissions?.projectAccess?.accessLevel
val projectGroupAccess: GitlabAccessLevel? = project.permissions?.projectGroupAccess?.accessLevel
private class GitlabProjectCache(project: Project) : Serializable {
val projectAccess: AccessLevel? = project.permissions?.projectAccess?.accessLevel
val projectGroupAccess: AccessLevel? = project.permissions?.groupAccess?.accessLevel
val owner: GitlabUserCache? = if (project.owner == null) null else GitlabUserCache(project.owner)
}

Expand All @@ -44,7 +44,7 @@ private class GitlabProjectCache(project: GitlabProject) : Serializable {
* @author Artem V. Navrotskiy <bozaro@users.noreply.github.com>
* @author Marat Radchenko <marat@slonopotamus.org>
*/
internal class GitLabAccess(local: LocalContext, config: GitLabMappingConfig, private val gitlabProject: GitlabProject, private val relativeRepoPath: Path, private val gitlabContext: GitLabContext) : VcsAccess {
internal class GitLabAccess(local: LocalContext, config: GitLabMappingConfig, private val gitlabProject: Project, private val relativeRepoPath: Path, private val gitlabContext: GitLabContext) : VcsAccess {
private val cache = local.shared.cacheDB.hashMap("gitlab.projectAccess.${gitlabProject.id}", Serializer.STRING, Serializer.JAVA)
.expireAfterCreate(config.cacheTimeSec, TimeUnit.SECONDS)
.expireAfterUpdate(config.cacheTimeSec, TimeUnit.SECONDS)
Expand All @@ -61,8 +61,8 @@ internal class GitLabAccess(local: LocalContext, config: GitLabMappingConfig, pr
if (user.isAnonymous) return false
val project = getProjectViaSudo(user) ?: return false
if (isProjectOwner(project, user)) return true
return hasAccess(project.projectAccess, GitlabAccessLevel.Developer)
|| hasAccess(project.projectGroupAccess, GitlabAccessLevel.Developer)
return hasAccess(project.projectAccess, AccessLevel.DEVELOPER)
|| hasAccess(project.projectGroupAccess, AccessLevel.DEVELOPER)
}

@Throws(IOException::class)
Expand All @@ -89,6 +89,11 @@ internal class GitLabAccess(local: LocalContext, config: GitLabMappingConfig, pr
hooksPayload["internal_socket"] = gitlabContext.config.gitalySocket
hooksPayload["internal_socket_token"] = gitlabContext.config.gitalyToken
hooksPayload["repository"] = gitalyRepoString

// TODO: need to get this from GitLab API
// hooksPayload["object_format"] = gitlabProject.repositoryObjectFormat
hooksPayload["object_format"] = "sha1"

hooksPayload["receive_hooks_payload"] = userDetails
hooksPayload["user_details"] = userDetails

Expand Down Expand Up @@ -121,8 +126,8 @@ internal class GitLabAccess(local: LocalContext, config: GitLabMappingConfig, pr
return owner.id.toString() == user.externalId || owner.name == user.username
}

private fun hasAccess(access: GitlabAccessLevel?, level: GitlabAccessLevel): Boolean {
return access != null && access.accessValue >= level.accessValue
private fun hasAccess(access: AccessLevel?, level: AccessLevel): Boolean {
return access != null && access >= level
}

@Throws(IOException::class)
Expand All @@ -138,11 +143,14 @@ internal class GitLabAccess(local: LocalContext, config: GitLabMappingConfig, pr
return cache.computeIfAbsent(key) { userId ->
try {
val result = if (userId.isEmpty()) {
GitlabAPI.connect(gitlabContext.gitLabUrl, null).getProject(gitlabProject.id)
GitLabApi(gitlabContext.gitLabUrl, null).use {
it.projectApi.getProject(gitlabProject.id)
}
} else {
val api = gitlabContext.connect()
val tailUrl = GitlabProject.URL + "/" + gitlabProject.id + "?sudo=" + userId
api.retrieve().to(tailUrl, GitlabProject::class.java)
GitLabApi(gitlabContext.gitLabUrl, gitlabContext.config.getToken().type, gitlabContext.config.getToken().value).use {
it.sudo(userId)
it.projectApi.getProject(gitlabProject.id)
}
}
SerializableOptional(GitlabProjectCache(result))
} catch (e: FileNotFoundException) {
Expand Down
Expand Up @@ -30,7 +30,7 @@ class GitLabHookEvent {
val pathWithNamespace: String? = null

@JsonProperty("project_id")
val projectId: Int? = null
val projectId: Long? = null

companion object {
private val mapper = ObjectMapper()
Expand Down

0 comments on commit 71481f9

Please sign in to comment.