Skip to content

Commit 86ad84a

Browse files
authored
Authenticate UrlChecker calls to API host (#2920)
This authenticates `UrlChecker` calls if the scheme and host of the request URL match `ForgeCfg#apiHost`.
1 parent 5630b04 commit 86ad84a

File tree

5 files changed

+59
-32
lines changed

5 files changed

+59
-32
lines changed

modules/core/src/main/scala/org/scalasteward/core/application/Context.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ object Context {
179179
scalafixMigrationsFinder0 <- scalafixMigrationsLoader0.createFinder(config.scalafixCfg)
180180
repoConfigLoader0 = new RepoConfigLoader[F]
181181
maybeGlobalRepoConfig <- repoConfigLoader0.loadGlobalRepoConfig(config.repoConfigCfg)
182-
urlChecker0 <- UrlChecker.create[F](config)
182+
urlChecker0 <- UrlChecker
183+
.create[F](config, ForgeSelection.authenticateIfApiHost(config.forgeCfg, forgeUser))
183184
kvsPrefix = Some(config.forgeCfg.tpe.asString)
184185
pullRequestsStore <- JsonKeyValueStore
185186
.create[F, Repo, Map[Uri, PullRequestRepository.Entry]]("pull_requests", "2", kvsPrefix)

modules/core/src/main/scala/org/scalasteward/core/forge/ForgeSelection.scala

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import cats.{Applicative, MonadThrow}
2121
import org.http4s.headers.Authorization
2222
import org.http4s.{BasicCredentials, Header, Request}
2323
import org.scalasteward.core.application.Config
24+
import org.scalasteward.core.application.Config.ForgeCfg
2425
import org.scalasteward.core.forge.ForgeType._
2526
import org.scalasteward.core.forge.azurerepos.AzureReposApiAlg
2627
import org.scalasteward.core.forge.bitbucket.BitbucketApiAlg
@@ -60,15 +61,26 @@ object ForgeSelection {
6061
forgeType match {
6162
case AzureRepos => _.putHeaders(basicAuth(user)).pure[F]
6263
case Bitbucket => _.putHeaders(basicAuth(user)).pure[F]
63-
case BitbucketServer =>
64-
// Bypass the server-side XSRF check, see
65-
// https://github.com/scala-steward-org/scala-steward/pull/1863#issuecomment-754538364
66-
val xAtlassianToken = Header.Raw(ci"X-Atlassian-Token", "no-check")
67-
_.putHeaders(basicAuth(user), xAtlassianToken).pure[F]
68-
case GitHub => _.putHeaders(basicAuth(user)).pure[F]
69-
case GitLab => _.putHeaders(Header.Raw(ci"Private-Token", user.accessToken)).pure[F]
64+
case BitbucketServer => _.putHeaders(basicAuth(user), xAtlassianToken).pure[F]
65+
case GitHub => _.putHeaders(basicAuth(user)).pure[F]
66+
case GitLab => _.putHeaders(Header.Raw(ci"Private-Token", user.accessToken)).pure[F]
7067
}
7168

7269
private def basicAuth(user: AuthenticatedUser): Authorization =
7370
Authorization(BasicCredentials(user.login, user.accessToken))
71+
72+
// Bypass the server-side XSRF check, see
73+
// https://github.com/scala-steward-org/scala-steward/pull/1863#issuecomment-754538364
74+
private val xAtlassianToken = Header.Raw(ci"X-Atlassian-Token", "no-check")
75+
76+
def authenticateIfApiHost[F[_]](
77+
forgeCfg: ForgeCfg,
78+
user: AuthenticatedUser
79+
)(implicit F: Applicative[F]): Request[F] => F[Request[F]] =
80+
req => {
81+
val sameScheme = req.uri.scheme === forgeCfg.apiHost.scheme
82+
val sameHost = req.uri.host === forgeCfg.apiHost.host
83+
if (sameScheme && sameHost) authenticate(forgeCfg.tpe, user)(F)(req)
84+
else req.pure[F]
85+
}
7486
}

modules/core/src/main/scala/org/scalasteward/core/util/UrlChecker.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ object UrlChecker {
4545
CaffeineCache(cache)
4646
}
4747

48-
def create[F[_]](config: Config)(implicit
48+
def create[F[_]](config: Config, modify: Request[F] => F[Request[F]])(implicit
4949
urlCheckerClient: UrlCheckerClient[F],
5050
logger: Logger[F],
5151
F: Sync[F]
@@ -59,7 +59,8 @@ object UrlChecker {
5959

6060
private def status(url: Uri): F[Status] =
6161
statusCache.cachingF(url.renderString)(None) {
62-
urlCheckerClient.client.status(Request[F](method = Method.HEAD, uri = url))
62+
val req = Request[F](method = Method.HEAD, uri = url)
63+
modify(req).flatMap(urlCheckerClient.client.status)
6364
}
6465
}
6566
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.scalasteward.core.forge
2+
3+
import cats.Id
4+
import munit.FunSuite
5+
import org.http4s.headers.{Accept, Authorization}
6+
import org.http4s.syntax.all._
7+
import org.http4s.{BasicCredentials, Headers, MediaType, Request}
8+
import org.scalasteward.core.forge.ForgeType.GitHub
9+
import org.scalasteward.core.forge.data.AuthenticatedUser
10+
import org.scalasteward.core.mock.MockConfig
11+
12+
class ForgeSelectionTest extends FunSuite {
13+
test("authenticate") {
14+
val obtained = ForgeSelection
15+
.authenticate[Id](GitHub, AuthenticatedUser("user", "pass"))
16+
.apply(Request(headers = Headers(Accept(MediaType.text.plain))))
17+
.headers
18+
val expected =
19+
Headers(Accept(MediaType.text.plain), Authorization(BasicCredentials("user", "pass")))
20+
assertEquals(obtained, expected)
21+
}
22+
23+
test("authenticateIfApiHost") {
24+
val forgeCfg = MockConfig.config.forgeCfg
25+
val auth = ForgeSelection.authenticateIfApiHost[Id](forgeCfg, AuthenticatedUser("user", "pass"))
26+
27+
val obtained1 = auth.apply(Request(uri = uri"http://example.com/foo/bar")).headers
28+
val expected1 = Headers(Authorization(BasicCredentials("user", "pass")))
29+
assertEquals(obtained1, expected1)
30+
31+
val obtained2 = auth.apply(Request(uri = uri"http://acme.org/foo/bar")).headers
32+
val expected2 = Headers()
33+
assertEquals(obtained2, expected2)
34+
}
35+
}

modules/core/src/test/scala/org/scalasteward/core/forge/github/authenticationTest.scala

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)