Repository: scala-steward-org/scala-steward (scala)

Issue:
Add release notes/changelog/version diff for dependencies in private GitHub repos
These nice features don't work for dependencies whose git repos are private (thus need auth):
- Release Notes
- Changelog
- Version Diff

This is probably because the http client used for those doesn't seem to use the github token:

https://github.com/fthomas/scala-steward/blob/master/modules/core/src/main/scala/org/scalasteward/core/util/HttpExistenceClient.scala

Pull request description:
This authenticates `UrlChecker` calls if the scheme and host of the request URL match `ForgeCfg#apiHost`.

Hopefully fixes #1468.

Patch:
diff --git a/modules/core/src/main/scala/org/scalasteward/core/application/Context.scala b/modules/core/src/main/scala/org/scalasteward/core/application/Context.scala
index 16a3f4f26d..7726ea5e4f 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/application/Context.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/application/Context.scala
@@ -179,7 +179,8 @@ object Context {
       scalafixMigrationsFinder0 <- scalafixMigrationsLoader0.createFinder(config.scalafixCfg)
       repoConfigLoader0 = new RepoConfigLoader[F]
       maybeGlobalRepoConfig <- repoConfigLoader0.loadGlobalRepoConfig(config.repoConfigCfg)
-      urlChecker0 <- UrlChecker.create[F](config)
+      urlChecker0 <- UrlChecker
+        .create[F](config, ForgeSelection.authenticateIfApiHost(config.forgeCfg, forgeUser))
       kvsPrefix = Some(config.forgeCfg.tpe.asString)
       pullRequestsStore <- JsonKeyValueStore
         .create[F, Repo, Map[Uri, PullRequestRepository.Entry]]("pull_requests", "2", kvsPrefix)
diff --git a/modules/core/src/main/scala/org/scalasteward/core/forge/ForgeSelection.scala b/modules/core/src/main/scala/org/scalasteward/core/forge/ForgeSelection.scala
index 8e609b1135..9e49a8eae0 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/forge/ForgeSelection.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/forge/ForgeSelection.scala
@@ -21,6 +21,7 @@ import cats.{Applicative, MonadThrow}
 import org.http4s.headers.Authorization
 import org.http4s.{BasicCredentials, Header, Request}
 import org.scalasteward.core.application.Config
+import org.scalasteward.core.application.Config.ForgeCfg
 import org.scalasteward.core.forge.ForgeType._
 import org.scalasteward.core.forge.azurerepos.AzureReposApiAlg
 import org.scalasteward.core.forge.bitbucket.BitbucketApiAlg
@@ -60,15 +61,26 @@ object ForgeSelection {
     forgeType match {
       case AzureRepos      => _.putHeaders(basicAuth(user)).pure[F]
       case Bitbucket       => _.putHeaders(basicAuth(user)).pure[F]
-      case BitbucketServer =>
-        // Bypass the server-side XSRF check, see
-        // https://github.com/scala-steward-org/scala-steward/pull/1863#issuecomment-754538364
-        val xAtlassianToken = Header.Raw(ci"X-Atlassian-Token", "no-check")
-        _.putHeaders(basicAuth(user), xAtlassianToken).pure[F]
-      case GitHub => _.putHeaders(basicAuth(user)).pure[F]
-      case GitLab => _.putHeaders(Header.Raw(ci"Private-Token", user.accessToken)).pure[F]
+      case BitbucketServer => _.putHeaders(basicAuth(user), xAtlassianToken).pure[F]
+      case GitHub          => _.putHeaders(basicAuth(user)).pure[F]
+      case GitLab          => _.putHeaders(Header.Raw(ci"Private-Token", user.accessToken)).pure[F]
     }
 
   private def basicAuth(user: AuthenticatedUser): Authorization =
     Authorization(BasicCredentials(user.login, user.accessToken))
+
+  // Bypass the server-side XSRF check, see
+  // https://github.com/scala-steward-org/scala-steward/pull/1863#issuecomment-754538364
+  private val xAtlassianToken = Header.Raw(ci"X-Atlassian-Token", "no-check")
+
+  def authenticateIfApiHost[F[_]](
+      forgeCfg: ForgeCfg,
+      user: AuthenticatedUser
+  )(implicit F: Applicative[F]): Request[F] => F[Request[F]] =
+    req => {
+      val sameScheme = req.uri.scheme === forgeCfg.apiHost.scheme
+      val sameHost = req.uri.host === forgeCfg.apiHost.host
+      if (sameScheme && sameHost) authenticate(forgeCfg.tpe, user)(F)(req)
+      else req.pure[F]
+    }
 }
diff --git a/modules/core/src/main/scala/org/scalasteward/core/util/UrlChecker.scala b/modules/core/src/main/scala/org/scalasteward/core/util/UrlChecker.scala
index 2ad5f8df93..d7437f2123 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/util/UrlChecker.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/util/UrlChecker.scala
@@ -45,7 +45,7 @@ object UrlChecker {
       CaffeineCache(cache)
     }
 
-  def create[F[_]](config: Config)(implicit
+  def create[F[_]](config: Config, modify: Request[F] => F[Request[F]])(implicit
       urlCheckerClient: UrlCheckerClient[F],
       logger: Logger[F],
       F: Sync[F]
@@ -59,7 +59,8 @@ object UrlChecker {
 
         private def status(url: Uri): F[Status] =
           statusCache.cachingF(url.renderString)(None) {
-            urlCheckerClient.client.status(Request[F](method = Method.HEAD, uri = url))
+            val req = Request[F](method = Method.HEAD, uri = url)
+            modify(req).flatMap(urlCheckerClient.client.status)
           }
       }
     }


Test patch:
diff --git a/modules/core/src/test/scala/org/scalasteward/core/forge/ForgeSelectionTest.scala b/modules/core/src/test/scala/org/scalasteward/core/forge/ForgeSelectionTest.scala
new file mode 100644
index 0000000000..df4de5051b
--- /dev/null
+++ b/modules/core/src/test/scala/org/scalasteward/core/forge/ForgeSelectionTest.scala
@@ -0,0 +1,35 @@
+package org.scalasteward.core.forge
+
+import cats.Id
+import munit.FunSuite
+import org.http4s.headers.{Accept, Authorization}
+import org.http4s.syntax.all._
+import org.http4s.{BasicCredentials, Headers, MediaType, Request}
+import org.scalasteward.core.forge.ForgeType.GitHub
+import org.scalasteward.core.forge.data.AuthenticatedUser
+import org.scalasteward.core.mock.MockConfig
+
+class ForgeSelectionTest extends FunSuite {
+  test("authenticate") {
+    val obtained = ForgeSelection
+      .authenticate[Id](GitHub, AuthenticatedUser("user", "pass"))
+      .apply(Request(headers = Headers(Accept(MediaType.text.plain))))
+      .headers
+    val expected =
+      Headers(Accept(MediaType.text.plain), Authorization(BasicCredentials("user", "pass")))
+    assertEquals(obtained, expected)
+  }
+
+  test("authenticateIfApiHost") {
+    val forgeCfg = MockConfig.config.forgeCfg
+    val auth = ForgeSelection.authenticateIfApiHost[Id](forgeCfg, AuthenticatedUser("user", "pass"))
+
+    val obtained1 = auth.apply(Request(uri = uri"http://example.com/foo/bar")).headers
+    val expected1 = Headers(Authorization(BasicCredentials("user", "pass")))
+    assertEquals(obtained1, expected1)
+
+    val obtained2 = auth.apply(Request(uri = uri"http://acme.org/foo/bar")).headers
+    val expected2 = Headers()
+    assertEquals(obtained2, expected2)
+  }
+}
diff --git a/modules/core/src/test/scala/org/scalasteward/core/forge/github/authenticationTest.scala b/modules/core/src/test/scala/org/scalasteward/core/forge/github/authenticationTest.scala
deleted file mode 100644
index 384a6be8b4..0000000000
--- a/modules/core/src/test/scala/org/scalasteward/core/forge/github/authenticationTest.scala
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.scalasteward.core.forge.github
-
-import cats.Id
-import munit.FunSuite
-import org.http4s.headers.{Accept, Authorization}
-import org.http4s.{BasicCredentials, Headers, MediaType, Request}
-import org.scalasteward.core.forge.ForgeSelection
-import org.scalasteward.core.forge.ForgeType.GitHub
-import org.scalasteward.core.forge.data.AuthenticatedUser
-
-class authenticationTest extends FunSuite {
-  test("addCredentials") {
-    val request = ForgeSelection
-      .authenticate[Id](GitHub, AuthenticatedUser("user", "pass"))
-      .apply(Request(headers = Headers(Accept(MediaType.text.plain))))
-
-    assertEquals(
-      request.headers,
-      Headers(Accept(MediaType.text.plain), Authorization(BasicCredentials("user", "pass")))
-    )
-  }
-}